Facing error in implementing props.history.push(/) in functional component - javascript

import React, { useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';
import { Button } from 'react-bootstrap';
import NavBarManu from './NavBarManu'
const Login = () => {
const [name, setName] = useState("");
const [password, setPassword] = useState("");
function login() {
fetch("http://localhost:3000/login?q=" + name).then((data) => {
data.json().then((resp) => {
console.warn("resp", resp)
if (resp.length > 0) {
localStorage.setItem('login', JSON.stringify(resp))
//Facing error
console.warn(this.props.history.push('list'))
}
else {
alert("Please check username and password")
}
})
})
}
return (
<div>
<br /><h2>Please Login !</h2><br />
<input type="text"
placeholder="enter name"
name="user" onChange={(event) => setName(event.target.value)} /> <br /> <br />
<input
placeholder="enter password"
type="password" name="password" onChange={(event) => setPassword(event.target.value)} /> <br /> <br />
<button onClick={() => { login() }} >Login</button>
</div>
);
};
export default Login;
I am facing error in console.warn(this.props.history.push('list')).
The syntax is for class component. That's why it is showing error. I am facing some difficulty in implementing it in functional components.
After the user press login button i want the page to be directed towards "list" page.
Please someone can point out my error or If there is any other better way to approach this then do advice me .

You are creating functional component and using class component syntax. Your props are empty.
this key word is used in class components to reference to state.
It should be like this:
console.warn(history.push('list'))
You have to import:
import { useHistory } from "react-router-dom";
And in Login component body:
import { useHistory } from "react-router-dom";
const Login = () => {
const [name, setName] = useState("");
const [password, setPassword] = useState("");
const history = useHistory()
function login() {
fetch("http://localhost:3000/login?q=" + name).then((data) => {
data.json().then((resp) => {
console.warn("resp", resp)
if (resp.length > 0) {
localStorage.setItem('login', JSON.stringify(resp))
//Facing error
console.warn(history.push('list'))
}
else {
alert("Please check username and password")
}
})
})
}
return ...

Related

search parameters in React Router 6

I am having issues with react-router#6 when implementing search parameters.
To begin with, the app can search using the search form (for example, if the user searches dark, the app would direct to localhost:3000/search?query=dark to display the results),
can also use the URL in the search bar to be directed to the right page and results (for example, if the user use the URL localhost:3000/search?query=dark, it will direct to the page and display the results). Now, the issue is when the user types in the search form, it changes the URL by adding search parameters instantly. I am aware that this is caused by the setSearchParams() in the handleChange function, but is there any way around this to NOT change the URL when typing in the search form?
import React from 'react'
import Navbar from './Navbar'
import create from 'zustand'
import { useState, useEffect } from 'react'
import { useLocation, useNavigate, useSearchParams } from 'react-router-dom'
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome'
import { faTriangleExclamation } from '#fortawesome/free-solid-svg-icons'
// Zustand
let store = (set) => ({
// input: '',
// setInput: (value) => set({ input: value }),
allImages: [],
setAllImages: (images) => set({ allImages: images}),
totalResults: null,
setTotalResults: (num) => set({ totalResults: num}),
})
export const useMain = create(store)
function Header() {
const [error, setError] = useState(null)
const [showError, setShowError] = useState(false)
const [fadeOut, setFadeOut] = useState(false)
const [page, setPage] = useState(1)
const [searchParams, setSearchParams] = useSearchParams()
const query = searchParams.get('query') || ''
const allImages = useMain(state => state.allImages)
const setAllImages = useMain(state => state.setAllImages)
// const totalResults = useMain(state => state.totalResults)
const setTotalResults = useMain(state => state.setTotalResults)
function handleChange(event) {
// setInput(event.target.value)
setSearchParams({query: event.target.value})
}
async function fetchImages() {
try {
const res = await fetch(`https://api.unsplash.com/search/photos?&page=${page}&per_page=30&query=${query}&client_id=${process.env.REACT_APP_UNSPLASH_API_KEY}`)
const data = await res.json()
if (data.total !== 0) {
setAllImages(data.results)
setTotalResults(data.total)
}
} catch(error) {
setError(error)
}
}
let navigate = useNavigate()
const handleSubmit = async (event) => {
event.preventDefault()
fetchImages()
navigate(`/search?query=${query}`)
}
const location = useLocation()
useEffect(() => {
if (location.pathname === '/search' && allImages.length === 0) {
fetchImages()
navigate(`/search?query=${query}`)
}
}, [query])
// error
useEffect(() => {
if (error) {
setShowError(true)
setTimeout(() => {
setFadeOut(true)
setTimeout(() => {
setShowError(false)
}, 1000)
}, 5000)
}
}, [error])
return (
<div className='header'>
<Navbar />
<h2 className='header--heading text-center text-light'>Find Images</h2>
<div className='header--form'>
<form onSubmit={handleSubmit}>
<input
className='header--form--input'
autoComplete='off'
type='text'
placeholder='Search'
onChange={handleChange}
name='input'
value={query}
/>
</form>
</div>
{showError && <div className={`network-error ${fadeOut ? 'fade-out' : ''}`}>
<i><FontAwesomeIcon icon={faTriangleExclamation} /></i>
<div className='network-error--message'>
<h5>Network Error</h5>
<p>Please check your Internet connection and try again</p>
</div>
</div>}
</div>
)
}
export default Header
import './App.css';
import Main from './components/Main';
import Search from './components/pages/Search'
import Favorites from './components/pages/Favorites';
import Error from './components/pages/Error';
import { BrowserRouter, Routes, Route } from 'react-router-dom'
import { SkeletonTheme } from 'react-loading-skeleton';
import { useDarkMode } from './components/Navbar';
function App() {
const darkMode = useDarkMode(state => state.darkMode)
let style
if (darkMode === 'light') {
style = 'wrapper'
} else {
style = 'wrapper-dark'
}
return (
<div className={style}>
<SkeletonTheme baseColor="#808080" highlightColor="#b1b1b1">
<BrowserRouter>
<Routes>
<Route path='/' element={<Main />} />
<Route path='search' element={<Search />} />
<Route path='favorites' element={<Favorites />} />
<Route path='*' element={<Error />} />
</Routes>
</BrowserRouter>
</SkeletonTheme>
</div>
);
}
export default App;
If I'm understanding your question correctly, you don't want to update the query queryString parameter in real-time as the form field is being updated, but sometime later, like when the form is submitted. Keep in mind that the setSeaarchParams function works just like the navigate function, but operates on the queryString.
You can manually update the searchParams object, and when the form is submitted, call setSearch params instead of navigate. Remove the value prop from the input element as we'll be updating the searchParams object.
function Header() {
...
const location = useLocation()
const navigate = useNavigate();
const [searchParams, setSearchParams] = useSearchParams()
...
function handleChange(event) {
searchParams.set("query", event.target.value);
}
...
const handleSubmit = async (event) => {
event.preventDefault();
fetchImages();
setSearchParams(searchParams);
}
...
return (
<div className='header'>
<Navbar />
<h2 className='header--heading text-center text-light'>Find Images</h2>
<div className='header--form'>
<form onSubmit={handleSubmit}>
<input
className='header--form--input'
autoComplete='off'
type='text'
placeholder='Search'
onChange={handleChange}
name='input'
/>
</form>
</div>
...
</div>
)
}
export default Header

User or pass wrong message in service api

I have this two codes
import React, { useState, useEffect, createContext } from "react";
import { useNavigate } from "react-router-dom";
import { api, createSession } from "./Services/api"
export const AuthContext = createContext();
export const AuthProvider = ({children}) => {
const navigate = useNavigate();
const [user, setUser] = useState();
const [loading, setLoading] = useState(true)
useEffect(() => {
const recoveredUser = localStorage.getItem("user")
if(recoveredUser){
setUser(JSON.parse(recoveredUser))
}
setLoading(false);
}, []);
const refreshPage = ()=>{
window.location.reload();
}
const login = async (username, pass) => {
const response = await createSession(username, pass);
const loggedUser = response.data.user;
const token = response.data.jwt;
localStorage.setItem("user", JSON.stringify(loggedUser))
localStorage.setItem("token", token)
api.defaults.headers.Authorization = {
"identifier": username,
"password": pass
}
setUser(loggedUser)
navigate("/");
};
const logout = () => {
localStorage.clear();
api.defaults.headers.Authorization = null;
setUser(null);
navigate("/login")
refreshPage();
};
return(
<AuthContext.Provider value={{authenticated: !!user, user, login, logout, loading}}> {children} </AuthContext.Provider>
)
}
and this
import axios from "axios";
export const api = axios.create({
baseURL: "https://communicationadmin.grupostra.com",
});
export const createSession = async(identifier, password) => {
return api.post('/auth/local', {identifier, password});
}
and this is my login screen
import Input from '../components/Input';
import styles from './Login.module.css';
import { useState, useContext } from 'react';
import { AuthContext } from '../Auth'
import logo from '../img/grupostra_horizontal.png'
import openEye from '../img/eye.png'
import closedEye from '../img/closedeye.png'
function Login(){
const { login } = useContext(AuthContext);
const [eyeImg, setEyeImg] = useState(openEye);
function handdleClick(){
if(eyeImg === openEye){
setEyeImg(closedEye);
document.querySelector('input:nth-child(4)').type = "";
} else {
setEyeImg(openEye)
document.querySelector('input:nth-child(4)').type = "password";
}
}
function handleSubmit(e){
e.preventDefault();
login(user, pass);
}
const [user, setUser] = useState('');
const [pass, setPass] = useState('');
return(
<div className={styles.container}>
<div className={styles.form_container}>
<img src={logo} alt="group" srcset="" />
<p>Welcome</p>
<form onSubmit={handleSubmit}>
<label htmlFor="username">Usuário</label>
<Input type="text" name="username" id="username" placeholder="User" setProps={setUser} value={user}/>
<label htmlFor="password">Senha</label>
<Input type="password" name="password" id="password" placeholder="Pass" setProps={setPass} value={pass}/>
<span onClick={handdleClick}><img src={eyeImg} className={styles.eyeImg} /></span>
<Input className="btn-hover" type="submit" name="submit" id="submit" value="Enter" />
</form>
</div>
</div>
)
}
export default Login
My question is how can or where a write an alert to show to users when your credentials are wrong? I tried some things, but i don't have sucess . (I want to show in a login screen) ........................................................................................
If the server handle the "wrong credentials" as an HTTP Code 4XX or 5XX then you can just catch the exception an do something about it
async handleSubmit(e){
e.preventDefault();
await login(user, pass).catch(exp => alert("Something happened, maybe invalid credentials"))
}
can you check your api response when it is giving error .when we are creating login api's if any error is there we are adding exceptions handlers to show those messages .if that is the case use below code
*
import Input from '../components/Input';
import styles from './Login.module.css';
import { useState, useContext } from 'react';
import { AuthContext } from '../Auth'
import logo from '../img/grupostra_horizontal.png'
import openEye from '../img/eye.png'
import closedEye from '../img/closedeye.png'
function Login(){
const { login } = useContext(AuthContext);
const [eyeImg, setEyeImg] = useState(openEye);
const [error,seterror]=useState('')
function handdleClick(){
if(eyeImg === openEye){
setEyeImg(closedEye);
document.querySelector('input:nth-child(4)').type = "";
} else {
setEyeImg(openEye)
document.querySelector('input:nth-child(4)').type = "password";
}
}
async function handleSubmit(e){
try
{
e.preventDefault();
awiat login(user, pass);
} catch(error){
seterror(error.message.toString())
}
}
const [user, setUser] = useState('');
const [pass, setPass] = useState('');
return(
<div className={styles.container}>
<div className={styles.form_container}>
<img src={logo} alt="group" srcset="" />
<p>Welcome</p>
<p>{error}</P>
<form onSubmit={handleSubmit}>
<label htmlFor="username">Usuário</label>
<Input type="text" name="username" id="username" placeholder="User" setProps={setUser} value={user}/>
<label htmlFor="password">Senha</label>
<Input type="password" name="password" id="password" placeholder="Pass" setProps={setPass} value={pass}/>
<span onClick={handdleClick}><img src={eyeImg} className={styles.eyeImg} /></span>
<Input className="btn-hover" type="submit" name="submit" id="submit" value="Enter" />
</form>
</div>
</div>
)
}
export default Login

How can I persist redux state

I'm developing a CRUD and, in one of the components, when the user creates the post, a div is rendered with the title and content values of his post. I need his posts to be saved even when he navigates between pages (without refreshing the page). Currently I can create as many posts as I want and they will be lined up one below the other, but when I go back to the previous page they are deleted. I tried to implement redux in this part the way I implemented it to save the user input (creating a slice to save the posts) but it didn't work. I know normally posts wouldn't be deleted, so I'd like to know where I'm going wrong.
signup screen:
import React, {useState, useEffect} from "react";
import "../_assets/signup.css";
import "../_assets/App.css";
import { useDispatch } from 'react-redux';
import userSlice from '../redux/userslice';
import { useNavigate } from "react-router-dom";
function Signup() {
const navigate = useNavigate();
const dispatch = useDispatch();
const [name, setName] = useState('')
const [buttonGrey, setButtonGrey] = useState('#cccccc')
useEffect(() => {
if (name!== '') {
setButtonGrey("black")
}
else {
setButtonGrey('#cccccc')
}
}, [name])
const handleSubmitForm= (e) => {
e.preventDefault()
dispatch(userSlice.actions.saveUser(name))
navigate("/main")
}
const handleChangeName = (text) => {
setName(text)
}
return (
<div className="container">
<div className="LoginBox">
<form onSubmit={handleSubmitForm}>
<h2>Welcome to codeleap network</h2>
<text>Please enter your username</text>
<input type="text" name="name" value={name} onChange = {e => handleChangeName(e.target.value)} placeholder="Jane Doe" />
<div className="button">
<button type="submit" style={{backgroundColor: buttonGrey}} disabled={!name} >
ENTER
</button>
</div>
</form>
</div>
</div>
);
}
export default Signup;
CRUD screen:
import React, { useState, useEffect } from "react";
import "../_assets/App.css";
import "../_assets/mainscreen.css";
import { MdDeleteForever } from "react-icons/md";
import { FiEdit } from "react-icons/fi";
import { useSelector } from 'react-redux';
import { useDispatch } from 'react-redux';
import { Navigate } from 'react-router-dom';
import postsSlice from '../redux/postsslice'
import Modal from "../components/modal.jsx";
function MainScreen() {
const dispatch = useDispatch();
const user = useSelector((state) => state.user)
const loadPosts = useSelector((state) => state.loadPosts)
const [title, setTitle] = useState("");
const [content, setContent] = useState("");
const [newPosts, setNewPosts] = useState([])
const [buttonGreyOut, setButtonGreyOut] = useState("#cccccc");
useEffect(() => {
if (title && content !== "") {
setButtonGreyOut("black");
} else {
setButtonGreyOut("#cccccc");
}
},[title, content]);
const handleSubmitSendPost = (e) => {
e.preventDefault();
setNewPosts(newPosts.concat({title, content}))
dispatch(postsSlice.actions.savePosts(newPosts))
setTitle('')
setContent('')
};
const handleChangeTitle = (text) => {
setTitle(text);
};
const handleChangeContent = (text) => {
setContent(text);
};
const [openModal, setOpenModal] = useState();
if (user === '') {
return <Navigate to="/" />
} else {
return (
<div className="containerMainScreen">
{openModal && <Modal closeModal={setOpenModal} />}
<div className="bar">
<h1>Codeleap</h1>
</div>
<div className="boxPost">
<h2 style={{ fontWeight: 700 }}>What's on your mind?</h2>
<h2>Title</h2>
<form onSubmit={handleSubmitSendPost}>
<input
type="text"
placeholder="Hello World"
name="name"
value={title}
onChange={(e) => handleChangeTitle(e.target.value)}
></input>
<h2>Content</h2>
<textarea
placeholder="Content"
name="content"
value={content}
onChange={(e) => handleChangeContent(e.target.value)}
></textarea>
<button
className="createButton"
type="submit"
style={{ backgroundColor: buttonGreyOut }}
disabled={!title || !content}
>
CREATE
</button>
</form>
</div>
{newPosts.map((post) => (
<div className="boxPost">
<div className="bar">
<h1>{post.title}</h1>
<MdDeleteForever
className="icon"
onClick={() => {
setOpenModal(true);
}}
/>
<FiEdit
style={{ color: "white", fontSize: "45px", paddingLeft: "23px" }}
/>
</div>
<div id="postowner">
<h3>#{user}</h3>
<h3>25 minutes ago</h3>
<br></br>
<textarea style={{ border: "none" }}>{post.content}</textarea>
</div>
</div>
))}
</div>
);
}
}export default MainScreen;
store.js:
import { configureStore } from '#reduxjs/toolkit';
import userSlice from './userslice';
import postsSlice from './postsslice'
export const store = configureStore({
reducer: {
user: userSlice.reducer,
loadPosts: postsSlice.reducer
},
})
postsSlice:
import { createSlice } from "#reduxjs/toolkit";
const postsSlice = createSlice({
name: "posts",
initialState: "",
reducers: {
savePosts: (state, action) => action.payload
}
});
export default postsSlice
CRUD screenshot:
https://i.stack.imgur.com/YoCJz.png
You are dispatching the savePosts action with the value of newPosts from before the current posts are added to it. The problem is in these lines:
setNewPosts(newPosts.concat({title, content}))
dispatch(postsSlice.actions.savePosts(newPosts))
Try something like this instead:
const posts = newPosts.concat({title, content})
setNewPosts(posts)
dispatch(postsSlice.actions.savePosts(posts))
It does not make sense to store the posts array in Redux and also store it in local component state. In my opinion you should ditch the component newPosts state and access the posts via Redux with useSelector.
I would also recommend dispatching an addPost action which requires just the current post. Let the reducer handle adding it to the array.
The state is an array of posts, so your initialState should be an empty array rather than an empty string.
const postsSlice = createSlice({
name: "posts",
initialState: [],
reducers: {
// add one post to the array.
addPost: (state, action) => {
state.push(action.payload); // modifies the draft state.
},
// replace the entire array.
replacePosts: (state, action) => action.payload
}
});
function MainScreen() {
const dispatch = useDispatch();
const user = useSelector((state) => state.user)
const posts = useSelector((state) => state.loadPosts)
const [title, setTitle] = useState("");
const [content, setContent] = useState("");
/* ... */
const handleSubmitSendPost = (e) => {
e.preventDefault();
dispatch(postsSlice.actions.addPost({title, content}))
setTitle('')
setContent('')
};

TypeError: props.onAdd is not a function React

I am getting "is not a function" error for a defined prop in my React application.
I tried adding onAdd={() => addTodo()}, however, it did not work.
I would like to know why the error is occurring and how to fix the error.
Please see the error picture
TodoForm.js - where I accept my props
import React, {useState} from 'react';
const TodoForm = (props) => {
const [input, setInput] = useState('');
const handleSubmit = e => {
// console.log(e);
e.preventDefault();
props.onAdd({
id: Math.floor(Math.random() * 10000),
text: input
});
setInput('');
};
return(
<div>
<form onSubmit={handleSubmit}>
<input type="text"
placeholder="Add a todo"
value={input} name="text"
className='todo-input'
onChange={e => setInput(e.target.value)} />
<input type="submit" value="Add todo"></input>
</form>
</div>
);
}
export default TodoForm;
TodoList.js
import React, { useState } from 'react';
import TodoForm from './TodoForm';
const TodoList = () => {
const [todos, setTodos] = useState([]);
const addTodo = todo => {
if(!todo.text || /^\s*$/.test(todo.text)){
return;
}
const newTodos = [todo, ...todos];
setTodos(newTodos);
console.log(...todos);
console.log("is working");
}
return(
<div>
<h1>Waht's the plan for today?</h1>
<TodoForm onAdd={() => addTodo()} />
</div>
);
}
export default TodoList;
Make sure you save the file , something is wrong , you code should still work , not how you want it to work but it should work , however the solution provided by Sean is 100% correct .
"onAdd={() => addTodo()}, however, it did not work." here you simply create a function that return a function.

I need to call the page render after POST request

I submit a request to the server and then want to get the result without reloading the page (SPA principle), how can this be done using useEffect()?
I tried to do something like this:
useEffect (() => {
addProduct ();
})
but it's was a bad idea
import React, {useState, useEffect} from 'react';
import api from './api';
const HandleProduct = () => {
const [name, setName] = useState('');
const [description, setDescription] = useState('');
const updateName = (e) =>{
setName(e.target.value);
}
const updateDescription = (e) =>{
setDescription(e.target.value);
}
const addProduct = () =>{
const product = {
name: name,
description: description
}
api.addProduct(product)
.then((req, res) =>{
console.log(res);
})
}
return (
<div>
<form onSubmit={addProduct}>
<input type="text" name="name" value={name} onChange={updateName}/>
<input type="text" name="description" value={description} onChange={updateDescription}/>
<button>Submit</button>
</form>
</div>
);
}
export default HandleProduct;
When the callback with response is called you've got the repsonse with all data sent from API. Let's assume you want to get ID. I will add new hook for storing ID, setting it after POST method is completed, and displaying it.
const [productId, setProductId] = useState(null);
const [name, setName] = useState('');
const [description, setDescription] = useState('');
const updateName = (e) =>{
setName(e.target.value);
}
const updateDescription = (e) =>{
setDescription(e.target.value);
}
onSubmit() {
const product = {
name: name,
description: description
}
api.addProduct(product)
.then((req, res) =>{
setProudctId(JSON.parse(res).id);
})
}
return (
<div>
{productId && <span>Your productId: {productId} </span>}
<form onSubmit={addProduct}>
<input type="text" name="name" value={name} onChange={updateName}/>
<input type="text" name="description" value={description} onChange={updateDescription}/>
<button>Submit</button>
</form>
</div>
);
}
export default HandleProduct;
Your code seems legit, yet, given that is not working, I'll give you another option to do it.
In App.js
<Router >
<ProductsProvider>
<Route exact path="/products" component={ProductsList} props={...props} />
<Route exact path={'/products/add'} component={HandleProduct}
props={...props} />
</ProductsProvider>
</Router>
In HandleProduct.js
import React, {useState} from 'react';
import api from './api';
import { Redirect } from 'react-router'
const HandleProduct = ({history}) => {
const [name, setName] = useState('');
const [description, setDescription] = useState('');
const updateName = (e) =>{
setName(e.target.value);
}
const updateDescription = (e) =>{
setDescription(e.target.value);
}
const addProduct = (e) =>{
e.preventDefault();
const product = {
name: name,
description: description
}
api.addProduct(product)
.then((req, res) =>{
history.push('/products');
})
}
return (
<div>
<form onSubmit={addProduct}>
<input type="text" name="name" value={name} onChange={updateName}/>
<input type="text" name="description" value={description} onChange={updateDescription}/>
<button>Submit</button>
</form>
</div>
);
}
import React, {useContext} from 'react';
import {ProductsContext} from './ProductsContext';
const ProductsList = () => {
const [data] = useContext(ProductsContext);
return (
<div>
{console.log(data)}
{data.products.map((product, index)=>(
<div key={index}>
<p>{product.name}</p>
<p><i>{product.description}</i></p>
</div>
))}
</div>
);
}
export default ProductsList;

Categories

Resources