React: Refresh component without reload after deleting an item - javascript

In my React App, I'm displaying all the books with author name. User can delete a book by clicking an item. Thing is I want to refresh the page without reloading the entire page. States are the way to go for such kind of situations but it still doesn't refresh the component.
Can anybody suggest any ideas?
App.tsx
import React, { useLayoutEffect, useState } from "react";
import { BrowserRouter, Route, Routes } from "react-router-dom";
import { getAllBooks } from "./api_actions/api_calls";
import "./App.css";
import AllBooks from "./components/AllBooks";
import InsertBooks from "./components/InsertBook";
import { Book } from "./models/Book";
function App() {
const [myBooks, setMyBooks] = useState<Book[]>([]);
useLayoutEffect(() => {
getAllBooks().then((orders) => {
setMyBooks(orders);
});
}, []);
return (
<div className="App">
<header className="App-header">
<BrowserRouter>
<Routes>
<Route path="/" element={<AllBooks books={myBooks} />} />
<Route path="/add" element={<InsertBooks />} />
</Routes>
</BrowserRouter>
</header>
</div>
);
}
export default App;
Component that displays all the books, AllBooks.tsx:
interface IAllBooksProps {
books: Book[];
}
const AllBooks: React.FC<IAllBooksProps> = (props) => {
const [lastDeletedTitle, setLastDeletedTitle] = useState("");
const handleDeleteBook = (title: string) => {
console.log("Trying to delete...", title);
deleteBook(title).then((response) => {
setLastDeletedTitle(title);
});
};
useEffect(() => {
if (lastDeletedTitle !== "") {
toast(`${lastDeletedTitle} has been deleted!`);
}
}, [lastDeletedTitle]);
return (
<>
{props.books?.map((book) => {
return <Card key={book.id} book={book} onDelete={handleDeleteBook} />;
})}
<ToastContainer />
</>
);
};

It is better not to call getAllBooks in App.tsx. You just need to call getAllBooks inside your delete function and useEffect in AllBooks.tsx. Try the code given below,
AllBooks.tsx
interface IAllBooksProps {
books: Book[];
}
const AllBooks: React.FC<IAllBooksProps> = (props) => {
const [lastDeletedTitle, setLastDeletedTitle] = useState("");
useEffect(() => {
getBooks();
}, [])
const getBooks = () => {
getAllBooks().then((orders) => {
setMyBooks(orders);
});
}
const handleDeleteBook = (title: string) => {
console.log("Trying to delete...", title);
deleteBook(title).then((response) => {
setLastDeletedTitle(title);
getBooks();
});
};
useEffect(() => {
if (lastDeletedTitle !== "") {
toast(`${lastDeletedTitle} has been deleted!`);
}
}, [lastDeletedTitle]);
return (
<>
{props.books?.map((book) => {
return <Card key={book.id} book={book} onDelete={handleDeleteBook} />;
})}
<ToastContainer />
</>
);
};

Bring changes in AllBooks.tsx like below.
const AllBooks: React.FC<IAllBooksProps> = (props) => {
const [books, setBooks] = useState(props.books); //props.books set in books state
const [lastDeletedTitle, setLastDeletedTitle] = useState("");
const handleDeleteBook = (title: string) => {
console.log("Trying to delete...", title);
deleteBook(title).then((response) => {
setLastDeletedTitle(title);
});
};
useEffect(() => {
if (lastDeletedTitle !== "") {
toast(`${lastDeletedTitle} has been deleted!`);
//recall the getAllBooks here
getAllBooks().then((orders) => {
setBooks(orders); //reset books
});
}
}, [lastDeletedTitle]);
return (
<>
//return books here
{books?.map((book) => {
return <Card key={book.id} book={book} onDelete={handleDeleteBook} />;
})}
<ToastContainer />
</>
);
};

Related

React search filter form

I have been trying to set a search filter form. I am getting data from API (an array of cake objects with "id", "cake_name", "category" etc properties), these get displayed properly. But somehow my search function is not working? It should allow the user to input a name of a cake which then would be filtered through the cakes available and only the searched one(s) would be displayed.
I am getting this error:
error
Here is my code:
context.js:
import React, { useState, useContext, useEffect } from "react";
import { useCallback } from "react";
const url = "https://cakeaddicts-api.herokuapp.com/cakes";
const AppContext = React.createContext();
const AppProvider = ({ children }) => {
const [loading, setLoading] = useState(false);
const [searchTerm, setSearchTerm] = useState("");
const [cakes, setCakes] = useState([]);
const [filteredData, setFilteredData] = useState([]);
const fetchCakes = async () => {
setLoading(true);
try {
const response = await fetch(url);
const cakes = await response.json();
if (cakes) {
const newCakes = cakes.map((cake) => {
const {
id,
image,
cake_name,
category,
type,
ingredients,
instructions,
} = cake;
return {
id,
image,
cake_name,
category,
type,
ingredients,
instructions,
};
});
setCakes(newCakes);
console.log(newCakes);
} else {
setCakes([]);
}
setLoading(false);
} catch (error) {
console.log(error);
setLoading(false);
}
};
useEffect(() => {
fetchCakes();
}, []);
return (
<AppContext.Provider
value={{
loading,
cakes,
setSearchTerm,
searchTerm,
filteredData,
setFilteredData,
}}
>
{children}
</AppContext.Provider>
);
};
// make sure use
export const useGlobalContext = () => {
return useContext(AppContext);
};
export { AppContext, AppProvider };
SearchForm.js
import React from "react";
import { useGlobalContext } from "../context";
import CakeList from "./CakeList";
const SearchForm = () => {
const { cakes, setSearchTerm, searchTerm, setFilteredData } =
useGlobalContext;
const searchCakes = () => {
if (searchTerm !== "") {
const filteredData = cakes.filter((item) => {
return Object.values(item)
.join("")
.toLowerCase()
.includes(searchTerm.toLowerCase());
});
setFilteredData(filteredData);
} else {
setFilteredData(cakes);
}
};
return (
<section className="section search">
<form className="search-form">
<div className="form-control">
<label htmlFor="name">Search Your Favourite Cake</label>
<input
type="text"
id="name"
onChange={(e) => searchCakes(e.target.value)}
/>
</div>
</form>
</section>
);
};
export default SearchForm;
CakeList.js:
import React from "react";
import Cake from "./Cake";
import Loading from "./Loading";
import { useGlobalContext } from "../context.js";
const CakeList = () => {
const { cakes, loading, searchTerm, filteredResults } = useGlobalContext();
if (loading) {
return <Loading />;
}
return (
<section className="section">
<h2 className="section-title">Cakes</h2>
<div className="cakes-center">
{searchTerm.length > 1
? filteredResults.map((cake) => {
return <Cake key={cake.id} {...cake} />;
})
: cakes.map((item) => {
return <Cake key={item.id} {...item} />;
})}
</div>
</section>
);
};
export default CakeList;
App.js:
import React from "react";
import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
// import pages
import Home from "./pages/Home";
import About from "./pages/About";
import SingleCake from "./pages/SingleCake";
import Error from "./pages/Error";
// import components
import Navbar from "./components/Navbar";
function App() {
return (
<Router>
<Navbar />
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/cake/:id" element={<SingleCake />} />
<Route path="*" element={<Error />} />
</Routes>
</Router>
);
}
export default App;
Can someone please help me with this search form? I have tried so many things and nothing is working :( Anyone?
On line 11 of SearchForm.js, there is a part that reads cakes.filter(. To resolve the typeError, change this to cakes?.filter(. This will only execute the filter if cakes is defined. It's a feature in javascript called Optional Chaining, introduced in ES2020.
Learn about it more here

ReactJS: Uncaught (in promise) TypeError: update is not a function // child component unable to update state of parent

Goal: Passing a function from parent to child that updates the values on parent component.
I've searched some other threads that were not using arrow functions that corrected their issue. I figure it is some sort of binding issue but I'm not sure where... This is sort of a dumbed down version of what I'm trying to do.
"react": "^18.1.0",
const Parent = () => {
const [value, setValue] = useState(0)
const update = () => {
setValue(value + 1)
}
return (
<>
{value}
<Child update={update} />
</>
)
}
I've tried passing the function a few different ways from parent to child.
<Child update={() => update()} />
<Child update={setValue} />
<Child update={() => setValue(value + 1)} />
<Child value={value} setValue={setValue} />
... and so on
const Child = ({ update }) => {
const handle = event => {
event.preventDefault()
update()
}
return (
<form onSubmit={handle}>
</form>
)
}
console.log shows update is a function in child component, and even shows the correct values to be updated - however when it is time for the function to be called I get that error.
FULL CODE
parent:
import React, { useEffect, useState, useRef } from 'react'
//Style
import { Container, Card, Button, Alert, Row, Col, Form } from 'react-bootstrap'
//Authentication
import { useAuth } from '../../authentication/AuthContext'
//Navigation
import { Link, useNavigate } from 'react-router-dom'
//Components
import Navigation from '../../components/Navigation'
import Loading from '../../components/Loading'
import CreateHOA from '../../components/CreateHOA'
import MapHOA from '../../components/MapHOA'
//Requests
import { addUser } from '../../requests/addUser'
import { getUser } from '../../requests/getUser'
const Dashboard = () => {
const [error, setError] = useState()
const [loading, setLoading] = useState(true)
const [database, setDatabase] = useState(null)
const [view, setView] = useState()
const [action, setAction] = useState({
createHoa: true
})
const { currentUser, logout } = useAuth()
const navigate = useNavigate()
const update = async () => {
getUser(currentUser.uid)
.then(res => {
console.log('get user', res)
if(res.data){
console.log('user exists')
console.log('set database')
setDatabase(res.data[0])
try{
console.log('check hoa exists')
if(res.data.hoa.length > 0){
console.log('hoa exists; set action/view')
setAction({...action, createHoa: false })
setView(res.data.hoa[0])
}
}catch(e){
console.log('hoa doesnt exist')
}
}else{
console.log('user doesnt exist')
addUser({ uid: currentUser.uid})
.then(res => {
console.log('add user', res)
console.log('set database')
setDatabase({ uid: currentUser.uid })
})
}
})
.then(() => {
console.log('set loading to false')
setLoading(false)
})
}
useEffect(() => {
update()
}, [])
return (
<>
{loading ? <Loading /> : <>
<Navigation />
<br />
<Container className='white-bg'>
<Row>
<Col xl={12}>
<h3 className='white'>Dashboard</h3>
<br /><br />
</Col>
</Row>
{action.createHoa ?
<CreateHOA uid={currentUser.uid} update={update} /> :
<>{currentUser.uid}</>
}
</Container>
<div className='footer'>
footer
</div>
</>}
</>
)
}
export default Dashboard
child
import React, { useState, useRef } from 'react'
//Style
import { Container, Card, Button, Alert, Row, Col, Form } from 'react-bootstrap'
//Components
import LoadingSmall from '../LoadingSmall'
//Requests
import { addHoa } from '../../requests/addHoa'
const CreateHOA = (uid, { update }) => {
const [loading, setLoading] = useState(false)
const nameRef = useRef()
const submit = event => {
event.preventDefault()
setLoading(true)
console.log('UID', uid)
addHoa(uid, nameRef.current.value).then(res => {
console.log(res)
update();
})
}
return (
<Row>
<Col xl={12}>
<Card>
<Card.Header>Action Needed</Card.Header>
<Card.Body>
{loading ? <LoadingSmall /> : <>
<Card.Title>Create an HOA</Card.Title>
<Card.Text>
<p>Type in the name of your Home Owners Association below and click create to get started!</p>
<Form onSubmit={submit}>
<Form.Group id='name'>
<Form.Control type='text' ref={nameRef} required />
</Form.Group>
<br />
<Button type='submit'>Create</Button>
</Form>
</Card.Text>
</>}
</Card.Body>
</Card>
</Col>
</Row>
)
}
export default CreateHOA
Use this.setValue() instead of just setValue():
const Parent = () => {
const [value, setValue] = useState(0)
const update = () => {
this.setValue(value + 1)
}
return (
<>
{value}
<Child update={update} />
</>
)
}

How do I refactor this into `withAuth` using HOC? Or is it possible to use hooks here in Next.js?

import { useSession } from 'next-auth/react'
import { LoginPage } from '#/client/components/index'
const Homepage = () => {
const session = useSession()
if (session && session.data) {
return (
<>
<div>Homepage</div>
</>
)
}
return <LoginPage />
}
export default Homepage
Basically, I don't want to write the same boilerplate of Login & useSession() on every page.
I want something like:
import { withAuth } from '#/client/components/index'
const Homepage = () => {
return (
<>
<div>Homepage</div>
</>
)
}
export default withAuth(Homepage)
Or if possible withAuthHook?
I currently have done the following:
import React from 'react'
import { useSession } from 'next-auth/react'
import { LoginPage } from '#/client/components/index'
export const withAuth = (Component: React.Component) => (props) => {
const AuthenticatedComponent = () => {
const session = useSession()
if (session && session.data) {
return <Component {...props} />
}
return <LoginPage />
}
return AuthenticatedComponent
}
But I get an error:
JSX element type 'Component' does not have any construct or call signatures.ts(2604)
If I use React.ComponentType as mentioned in the answer below, I get an error saying:
TypeError: (0 , client_components_index__WEBPACK_IMPORTED_MODULE_0_.withAuth) is not a function
Have you tried:
export const withAuth = (Component: React.ComponentType) => (props) => {
...
https://flow.org/en/docs/react/types/#toc-react-componenttype
Edit:
Try like this:
export const withAuth = (Component: React.ComponentType) => (props) => {
const session = useSession()
if (session && session.data) {
return <Component {...props} />
}
return <LoginPage />
}
return AuthenticatedComponent
}
The answer was hidden in the docs. I had to specify the following Auth function in _app.tsx:
import { useEffect } from 'react'
import { AppProps } from 'next/app'
import { SessionProvider, signIn, useSession } from 'next-auth/react'
import { Provider } from 'urql'
import { client } from '#/client/graphql/client'
import '#/client/styles/index.css'
function Auth({ children }: { children: any }) {
const { data: session, status } = useSession()
const isUser = !!session?.user
useEffect(() => {
if (status === 'loading') return
if (!isUser) signIn()
}, [isUser, status])
if (isUser) {
return children
}
return <div>Loading...</div>
}
interface AppPropsWithAuth extends AppProps {
Component: AppProps['Component'] & { auth: boolean }
}
const CustomApp = ({ Component, pageProps: { session, ...pageProps } }: AppPropsWithAuth) => {
return (
<SessionProvider session={session}>
<Provider value={client}>
{Component.auth ? (
<Auth>
<Component {...pageProps} />
</Auth>
) : (
<Component {...pageProps} />
)}
</Provider>
</SessionProvider>
)
}
export default CustomApp
And on my actual page, I had to specify Component.auth as true:
const Homepage = () => {
return (
<>
<div>Homepage</div>
</>
)
}
Homepage.auth = true
export default Homepage
A nice summary of what it does can be found on https://simplernerd.com/next-auth-global-session

My .filter in react lost when refresh page

I'm trying create a search bar, when user want to search a product.
Here is my Search Input:
const [searchTerm, setSearchTerm] = useState("");
const onSubmit = (e) => {
e.preventDefault();
navigate(`/search/${searchTerm}`);
setIsShowing(false);
setOpacity(1);
};
<FormSearch onSubmit={onSubmit}>
<SearchInput type="text"
placeholder="Type something to search"
onChange={(e)=> setSearchTerm(e.target.value)}
defaultValue={searchTerm} />
<SearchButton type="submit" value="Search" />
</FormSearch>
and here is the router when click search and take user to another page:
<Router>
<SearchInfo
path="/search/:title "
searchTerm={searchTerm}
/>
</Router>
and here is my react function for the page after search:
import React, { useEffect, useState } from "react";
import styled from "styled-components";
const SearchInfo = (props) => {
const [products, setProducts] = useState([]);
const getProductsAPI = () => {
axios
.get("http://localhost:8000/api/products")
.then((res) => {
setProducts(res.data);
})
.catch((err) => {
console.log(err);
});
};
useEffect(() => {
getProductsAPI();
}, [props]);
const InfoWrapper = styled.div`
text-align: center;
`;
return (
<div>
<InfoWrapper>
{products
.filter((product) =>
product.title.includes(props.searchTerm.toUpperCase())
)
.map((filteredItem, i) => (
<Item key={i}>
<ItemTitle> {filteredItem.title} </ItemTitle>
</Item>
))}
</InfoWrapper>
</div>
);
};
export default SearchInfo;
if I refresh the page it will show all my products instead of just props.searchTerm.
How can I fix this? Seems like the props I passed from route didn't session
The searchTerm comes from the state and props you pass, not from the url. Youll need to get the param from the Router and use that instead, see https://reactrouter.com/web/api/Hooks/useparams
Something like:
<Router>
<SearchInfo path="/search/:searchterm"/>
</Router>
import { useParams } from "react-router-dom";
const SearchInfo = (props) => {
let { searchterm } = useParams();
// ...
return (
<div>
<InfoWrapper>
{products.filter((product) => product.title.includes(searchterm))
.map((filteredItem, i) => (
<Item key={i}>
<ItemTitle> {filteredItem.title} </ItemTitle>
</Item>
))}
</InfoWrapper>
</div>
);
};
I don't know why your SearchInfo have path as prop but I think path is supposed to be managed by router, so the ideal structure would be:
<Router path="/search/:searchterm" component={SearchInfo} />
Then you can easily access to location info:
const SearchInfo = (props) => {
// Here is what you need
const {
match: { params },
} = props;
}

Redirecting using useEffect hook

The idea is that I've got a component that renders something but in the meantime is checking something that will return or redirect to another component:
useEffect(() => {
(() => {
if (true) {
// return to one component
}
// return to another component
})();
});
return (
<div> Javier </div>
);
I think that it is possible using the useEffect hook, but the problem is that, it does not redirect to my components, I tried using Redirect from the react-router, returning the component itself, and also using the history package, in this case, only replaced the url but no redirection at all.
Is this possible? Or maybe I'm way off the point.
Thanks a lot!
if you are just needing conditional rendering you could do something like this:
const LoadingComponent = () => <div> Javier </div>
function Landing(props) {
const [state={notLoaded:true}, setState] = useState(null);
useEffect(() => {
const asyncCallback = async () =>{
const data = await axios.get('/someApiUrl')
setState(data)
}
asyncCallback()
},[]);
if(!state){
return <FalseyComponent />
}
if(state.notLoaded){
//return some loading component(s) (or nothing to avoid flicker)
return <LoadingComponent /> // -or- return <div/>
}
return <TruthyComponent />
}
or redirect completely:
const LoadingComponent = () => <div> Javier </div>
function Landing(props) {
const [state={notLoaded:true}, setState] = useState(null);
useEffect(() => {
const asyncCallback = async () =>{
const data = await axios.get('/someApiUrl')
setState(data)
}
asyncCallback()
},[]);
if(!state){
return <Redirect to='/falseyRoute' />
}
if(state.notLoaded){
//return some loading component(s) or (nothing to avoid flicker)
return <LoadingComponent /> // -or- return <div/>
}
return <Redirect to='/truthyRoute' />
}
Using React router v6 you can create a redirection using useEffect:
import React, { useEffect } from 'react';
import {
BrowserRouter, Route, Routes, useNavigate,
} from 'react-router-dom';
const App = () => (
<div>
<BrowserRouter>
<Routes>
<Route path="/" element={<Main />} />
<Route path="/home" element={<Home />} />
</Routes>
</BrowserRouter>
</div>
);
const Main = () => {
const navigate = useNavigate();
useEffect(() => {
let didCancel = false;
const goToHomePage = () => navigate('/home');
if (!didCancel) { goToHomePage(); }
return () => { didCancel = true; };
}, [navigate]);
return (
<div>
<h1>Welcome Main!</h1>
</div>
);
};
const Home = () => (
<div>
<h1>Welcome Home!</h1>
</div>
);
export default App;
If you want to create an alternative redirection to another component, you can do it as below:
import React, { useEffect } from 'react';
import {
BrowserRouter, Route, Routes, useNavigate,
} from 'react-router-dom';
const App = () => (
<div>
<BrowserRouter>
<Routes>
<Route path="/" element={<Main />} />
<Route path="/home" element={<Home />} />
<Route path="/other" element={<Other />} />
</Routes>
</BrowserRouter>
</div>
);
const Main = () => {
const navigate = useNavigate();
useEffect(() => {
let didCancel = false;
const goToHomePage = () => navigate('/home');
const goToOtherPage = () => navigate('/other');
if (!didCancel) { goToHomePage(); } else { goToOtherPage(); }
return () => { didCancel = true; };
}, [navigate]);
return (
<div>
<h1>Welcome Main!</h1>
</div>
);
};
const Home = () => (
<div>
<h1>Welcome Home!</h1>
</div>
);
const Other = () => (
<div>
<h1>Welcome Other!</h1>
</div>
);
export default App;
In React router 5 with changed old syntax it should also work. However, in React router 6 I did not find Redirect so the above redirection is more useful.
Try to return based on some state value like this.
import { Redirect } from "react-router-dom"; //import Redirect first
const [redirctTo, setRedirctTo] = useState(false); // your state value to manipulate
useEffect(() => {
(() => {
if (true) {
setRedirctTo(true)
}
// return to another component
})();
});
if(redirctTo){
return <Redirect to="/your-url" />
} else {
return (
<div> Javier </div>
);
}

Categories