I have 2 sibling components namely <AddTask/> and <TaskList/> which are children of <Home/> component. Currently, when I add a new task in my ToDo App, it will be added but I need to refresh the page in order for it to display the new task. How can I refresh the <TaskList/> component immediately after I click the Add button in <AddTask/> component?
Here is my <AddTask/> Component
const AddTask = () => {
const [task, setTask] = useState("");
const [isPending, setIsPending] = useState(false);
const handleClick = (e)=> {
e.preventDefault();
setIsPending(true);
const todo = {task};
fetch('http://localhost:8000/tasks', {
method: 'POST',
headers: { "Content-Type": "application/json" },
body: JSON.stringify(todo)
})
.then(()=>{
setIsPending(false);
})
};
return (
<form className="new-task" onSubmit={handleClick}>
<input className="input"
type="text"
required
value={task}
onChange= { (e)=> setTask(e.target.value) }
/>
<button className="add-task">Add</button>
</form>
);
}
export default AddTask;
This is the <Home/> Component
import TaskList from "./TaskList";
import useFetch from "./useFetch";
const Home = () => {
const { data: task, isPending, error} = useFetch('http://localhost:8000/tasks');
return (
<div className="home">
<AddTask />
{ error && <div>Failed to fetch data.</div> }
{ isPending && <div>Loading...</div> }
{ task && <TaskList task={task} /> }
</div>
);
}
export default Home;
In Home component, you need a tasks state so you can update that state in AddTask component
Home
import TaskList from "./TaskList";
import useFetch from "./useFetch";
import { useState, useEffect } from 'react'
const Home = () => {
const [tasks, setTasks] = useState(null);
const { data: task, isPending, error} = useFetch('http://localhost:8000/tasks');
useEffect(() => {
if (task) setTasks(task)
}, [task])
return (
<div className="home">
<AddTask setTasks={setTasks} />
{ error && <div>Failed to fetch data.</div> }
{ isPending && <div>Loading...</div> }
{ tasks && <TaskList task={tasks} /> }
</div>
);
}
export default Home;
AddTask
const AddTask = ({ setTasks }) => {
const [task, setTask] = useState("");
const [isPending, setIsPending] = useState(false);
const handleClick = (e)=> {
e.preventDefault();
setIsPending(true);
const todo = {task};
fetch('http://localhost:8000/tasks', {
method: 'POST',
headers: { "Content-Type": "application/json" },
body: JSON.stringify(todo)
})
.then(()=>{
setIsPending(false);
setTasks(prev => ([...prev, task]))
})
};
return (
<form className="new-task" onSubmit={handleClick}>
<input className="input"
type="text"
required
value={task}
onChange= { (e)=> setTask(e.target.value) }
/>
<button className="add-task">Add</button>
</form>
);
}
export default AddTask;
Related
NOTE: My Page Card component is working correctly. How can I filter the card page component in the Search component?
I'm new to react, and I don't quite understand how I can accomplish this task.
In the Search component I put it in a fixed way, as I can't filter a component using another.
The Code is summarized for ease.
Card Page
import React, {
useEffect,
useState
} from "react";
import classes from "./boxService.module.css";
import axios from "axios";
function BoxService() {
const [test, SetTest] = useState([]);
useEffect(() => {
axios
.get("http://localhost:8080/api/test")
.then((response) => {
SetTest(response.data);
})
.catch(() => {
console.log("Error!");
});
}, []);
return ({
test.map((test, key) => {
<div className={classes.box}
return (
<Grid item xs = {2} key={key} >
<div className={test.name} < div >
<p className={test.description}</p>
</Grid>
);
})}
);
}
export default BoxService;
Seach Page
import React, {
useState,
useEffect
} from "react";
import axios from "axios";
function Search() {
const [searchTerm, setSearchTerm] = useState("");
const [test, SetTest] = useState([]);
//Chamada API
useEffect(() => {
axios
.get("http://localhost:8080/api/test")
.then((response) => {
SetTest(response.data);
})
.catch(() => {
console.log("Error");
});
}, []);
return (
<div>
<input type = "text"
placeholder = "Search..."
onChange = {
(event) => {
setSearchTerm(event.target.value);
}
}/>
{
test.filter((val) => {
if (searchTerm === "") {
return val;
} else if (
val.nome.toLowerCase().includes(searchTerm.toLowerCase())
) {return val;}
}).map((val, key) => {
return ( <div className = "user"
key = {key} >
<p> {val.name} </p> </div>
);
})
} </div>
);
}
export default Search;
Here is an example of how it should/could look like:
import React from "react";
function SearchBox({ setSearchTerm, searchTerm }) {
const handleFilter = (e) => {
setSearchTerm(e.target.value);
};
return (
<>
filter
<input type="search" onChange={handleFilter} value={searchTerm} />
</>
);
}
export default function App() {
const [searchTerm, setSearchTerm] = React.useState("");
const [filteredResults, setFilteredResults] = React.useState([]);
const [results, setResults] = React.useState([]);
React.useEffect(() => {
const fetchdata = async () => {
const randomList = await fetch(`https://randomuser.me/api/?results=50`);
const data = await randomList.json();
const { results } = data;
setResults(results);
};
fetchdata();
}, []);
React.useEffect(() => {
const filterResults = results.filter((item) =>
item.name.last.toLowerCase().includes(searchTerm.toLowerCase())
);
setFilteredResults(filterResults);
}, [searchTerm, results]);
return (
<div className="App">
<SearchBox setSearchTerm={setSearchTerm} searchTerm={searchTerm} />
<div>
<ul>
{filteredResults.map(({ name }, idx) => {
return (
<li key={idx}>
{name.first} {name.last}
</li>
);
})}
</ul>
</div>
</div>
);
}
I need DebounceInput to read information from my keyboard, but when I type in Ervin for example, the whole list stays and not just Ervin as it used to work. I don't really know where I went wrong. I know that Debounce is supposed to wait until the user stops typing, but even after waiting a minute nothing changed.
This is my files:
DebounceInput:
import {DebounceInput} from 'react-debounce-input';
type Props ={
onChange:Function;
}
const Debounce:React.FC<Props> = ({onChange}) => {
return(
<DebounceInput
onChange={(e) => onChange(e.target.value)}
debounceTimeout={500}
className="SearchInput"
placeholder="Search by user name..."
/>
)
}
export default Debounce
UsersList:
import {UsersContext} from '../../contexts/Users'
type Props = {
filteredUsers:Array<Person>;
}
type Person = {
name:string;
username:string;
}
const UsersList: React.FC = () => {
const filteredUsers = useContext(UsersContext);
return(
<div className="ListHead">
<ol className="list">
{filteredUsers.map((Person) => (
<li key={Person.name}>
<span>{Person.name}</span>
#{Person.username}
</li>
))}
</ol>
</div>
)}
Users:
import React, {useState, useEffect} from 'react';
import axios from 'axios';
type Users = Person[];
type Person = {
name:string;
username:string;
}
export const UsersContext = React.createContext<Users>([]);
const UsersProvider:React.FC= ({children}) => {
const [users, setUsers] = useState<Person[]>([]);
useEffect(() => {
axios
.get(`https://jsonplaceholder.typicode.com/users`)
.then((response) =>{
setUsers(response.data);
})
.catch((error) => {
console.log(error);
});
},[]);
return (
<UsersContext.Provider value={users}>
{children}
</UsersContext.Provider>
)
}
And Main:
const Main:React.FC= () => {
const [showUser, setShowUser] = useState("");
return (
<div>
<div>
<Header/>
</div>
<UsersProvider>
<UsersList />
<Debounce onChange={setShowUser}/>
</UsersProvider>
</div>
)
}
Your debouncing Input does not do anything except for changing the showUser state. Since your users resides in the UsersProvider . You can pass the value of your showUser as a prop to UsersProvider .
const Main:React.FC= () => {
const [showUser, setShowUser] = useState("");
return (
<div>
<div>
<Header/>
</div>
<UsersProvider searchText={showUser}>
<UsersList />
<Debounce onChange={setShowUser}/>
</UsersProvider>
</div>
)
}
Now inside your UsersProvider you can do the following
type UsersProviderProps = {
children: React.ReactNode;
searchText: string;
}
export const UsersContext = React.createContext<Users>([]);
const UsersProvider:React.FC<UsersProviderProps>= ({children, searchText}) => {
const [users, setUsers] = useState<Person[]>([]);
useEffect(() => {
axios
.get(`https://jsonplaceholder.typicode.com/users`)
.then((response) =>{
setUsers(response.data);
})
.catch((error) => {
console.log(error);
});
},[]);
const filteredUsers = useMemo(() => {
if(searchText.trim().length > 0 && users.length > 0){
return users.filter((person) =>
person.name.toLowerCase().includes(searchText.toLowerCase())
);
} else {
return users;
}
}, [searchText, users])
return (
<UsersContext.Provider value={filteredUsers}>
{children}
</UsersContext.Provider>
)
}
import React, { useState } from 'react'
import Display from './components/Display';
const App = () => {
const [input,setInput] = useState("");
const getData = async () => {
const myAPI = await fetch(`http://api.openweathermap.org/data/2.5/weather?q=${input}&units=metric&appid=60dfee3eb8199cac3e55af5339fd0761`);
const response = await myAPI.json();
console.log(response); //want to use response as a prop in Display component
}
return(
<div className="container">
<h1>Weather Report</h1>
<Display title={"City Name :"} /> //here
<Display title={"Temperature :"} /> //here
<Display title={"Description :"} /> //here
<input type={input} onChange={e => setInput(e.target.value)} className="input"/>
<button className="btn-style" onClick={getData}>Fetch</button>
</div>
);
}
export default App;
I don't know if I understand you correctly but if I'm right you want to access data returned from your function that is fetching from API, if so you can try this way
import React, { useState, useEffect } from 'react'
import Display from './components/Display';
import axios from 'axios';
const App = () => {
const [input,setInput] = useState("");
const [state, setState] = useState({loading: true, fetchedData: null});
useEffect(() => {
getData();
}, [setState]);
async function getData() {
setState({ loading: true });
const apiUrl = 'http://api.openweathermap.org/data/2.5/weather?q=${input}&units=metric&appid=60dfee3eb8199cac3e55af5339fd0761';
await axios.get(apiUrl).then((repos) => {
const rData = repos.data;
setState({ loading: false, fetchedData: rData });
});
}
return(
state.loading ? <CircularProgress /> : (
<List className={classes.root}>
{ state.fetchedData.map((row) => (
<div className="container">
<h1>Weather Report</h1>
<Display title={"City Name :" + row.cityName } /> //here
<Display title={"Temperature :" + row.temperature} /> //here
<Display title={"Description :" + row.description} /> //here
</div>
)) }
</List>
)
);
}
i have created a login form where user need to input his email id and OTP. below is my code -
import { useState } from 'react';
import axios from '../api/axios';
const useLogin = () => {
const [user, setUser] = useState(false);
const auth = async (value, OTP) => {
let config = {
method: 'POST',
url: '/api/user/generateToken',
headers: {
Authorization: 'value'
},
data: {
username: value,
password: OTP
}
};
try {
const response = await axios(config);
if (response.data.Status === "Failure") {
throw response.data.Message;
} else {
setUser(true);
return { status: response.data.Status, isAuth: user }
}
} catch (err) {
setUser(false);
return { status: undefined, message: err, isAuth: user };
}
}
return { auth, user };
}
export default useLogin
Everything is working fine here only problem is when i'm calling this function in my component i'll receive isAuth always false. Below is my component code -
import React, { Fragment, useRef, useEffect, useState } from 'react';
import { useLocation, useHistory } from "react-router-dom";
import { css } from "#emotion/core";
import ScaleLoader from "react-spinners/ScaleLoader";
import '../css/login.css';
import '../css/common.css';
import logo from '../assets/engageLogo.png';
import * as Yup from 'yup';
import { useFormik } from 'formik';
import useLogin from './../hooks/useOTP';
const override = css`
display: block;
margin: 0 auto;
border-color: #fff;
`;
const OTP = () => {
const [loading, setLoading] = useState(false);
const [color] = useState("#ffffff");
const [APIResponse, setAPIResponse] = useState(false);
const [APIMessage, setAPIMessage] = useState('')
const login = useLogin();
const location = useLocation();
const history = useHistory();
const inputRef = useRef();
const readRef = useRef();
const buttonRef = useRef();
const schema = Yup.object({
otp: Yup.string().required("OTP is Required")
});
const handleChangeError = () => {
return setAPIResponse(false)
}
const {
handleSubmit,
handleChange,
handleBlur,
touched,
errors,
} = useFormik({
initialValues: {
otp: "",
},
validationSchema: schema,
onSubmit: (values) => {
console.log(JSON.stringify(values));
buttonRef.current.disabled = true;
setLoading(true);
const loginCall = login.auth(location.state.email, values.otp);
loginCall.then(response => {
if (response.status === undefined || response.status === null) {
setLoading(false);
buttonRef.current.disabled = false;
setAPIResponse(true)
setAPIMessage(response.message)
} else {
setLoading(false);
history.push({
pathname: '/dashboard',
state: { email: values.email }
});
}
})
},
});
useEffect(() => {
inputRef.current.focus();
readRef.current.value = location.state.email;
}, [location])
return <Fragment>
<div className="centered-form">
<div className="centered-form__box">
<div className="mb-3 text-center">
<img src={logo} className="img-fluid" width="150" alt="Logo" />
</div>
<form onSubmit={handleSubmit} noValidate>
<div className="mb-3">
<label htmlFor="readEmail" className="form-label">Email</label>
<input
type="text"
name="readEmail"
id="readEmail"
ref={readRef}
className="form-control" readOnly />
</div>
<div className="mb-3">
<label htmlFor="otp" className="form-label">OTP</label>
<input
type="text"
name="otp"
id="otp"
ref={inputRef}
onChange={(e) => { handleChange(e); handleChangeError(e) }}
onBlur={handleBlur}
className="form-control" placeholder="Enter OTP" required />
{touched.otp && errors.otp
? <div className="invalid-feedback">Please enter valid OTP</div>
: null}
{APIResponse
? <div className="invalid-feedback">{APIMessage}</div>
: null}
</div>
<div className="d-grid gap-2">
<button ref={buttonRef} className="btn btn-main">{loading ?
<ScaleLoader color={color} loading={loading} css={override} height={15} /> : <span>Login</span>}</button>
</div>
</form>
</div>
</div>
</Fragment>
}
export default OTP
in response of loginCall i'll always get isAuth: false.
I want to use isAuth for protecting my routes. Just to check whether user has logged in or not.
why setUser is not updating the value here.
thanks in advance...
That's because by the time you returning your isAuth value the new user value is not set yet. you need to know that React setState is asynchronous function.
just use the the boolean itself directly like this:
setUser(true);
return { status: response.data.Status, isAuth: true }
or in case of a rejection:
setUser(false);
return { status: undefined, message: err, isAuth: false };
I have a form with input fields 'title' and 'body', they are being added to MongoDB after submitting the form, but the new post item shows up only after I refresh the page (that of course is not how it should work), but I want to know what I am doing wrong and how to fix the handleSumbit function?
Here is my code:
import { useState, useEffect } from "react";
import axios from "axios";
import "./App.css";
function App() {
const [title, setTitle] = useState("");
const [body, setBody] = useState("");
const [posts, setPosts] = useState([]);
const url = "http://localhost:8005/";
useEffect(() => {
const fetchPosts = async () => {
const response = await axios(`${url}posts`);
setPosts(response.data);
};
fetchPosts();
}, []);
const handleTitleChange = (event) => {
setTitle(event.target.value);
};
const handleBodyChange = (event) => {
setBody(event.target.value);
};
const handleSubmit = (event) => {
event.preventDefault();
axios.post(`${url}posts`, { title: title, body: body })
.then((res) => {
console.log(res.data);
});
alert("Post added!");
setTitle("");
setBody("");
};
console.log(posts);
return (
<div className="App">
{posts.map((post) => (
<div className="post" key={post.id}>
<h4>{post.title}</h4>
<p>{post.body}</p>
</div>
))}
<form onSubmit={handleSubmit}>
<label>
Mew post:
<input
type="text"
name="title"
placeholder="Add title"
onChange={handleTitleChange}
/>
<input
type="text"
name="body"
placeholder="Add body"
onChange={handleBodyChange}
/>
</label>
<button type="submit">Add</button>
</form>
</div>
);
}
export default App;
The problem is that you didn't update the posts state after you sent the axios post request. Edit the below code block:
const handleSubmit = (event) => {
event.preventDefault();
axios.post(`${url}posts`, { title: title, body: body })
.then((res) => {
console.log(res.data);
// save the new post to posts
setPosts([...posts, res.data])
});
alert("Post added!");
setTitle("");
setBody("");
};