history.push() does not work on the same page using react - javascript

I am building a listing page where products are displayed and i have a search window in my header (on every page).
The search window works fine. I enter a searchword, it forwards to the listing page and it gives me the results. This works on every site, except when i am already on the listing page. If i enter a searchword while i am on the listing page, it changes the url, but nothing else.
Code Search: The Searchinput triggers the change and is a component inside Search and looks as follows:
import React, { useState, useRef } from 'react';
import { LISTING_POSTS_PAGE } from 'settings/constant';
import { HOME_PAGE } from 'settings/constant';
const SearchInput = ({history}) => {
const [searchword, setSearchword] = useState('');
const submitHandler = (e) => {
e.preventDefault();
history.search= '?'+searchword;
history.push({
pathname: LISTING_POSTS_PAGE,
})
}
return (
<form className="search" onSubmit={submitHandler}>
<div className = "row">
<input
type = "text"
searchword = "q"
id = "q"
placeholder = "What do you want to buy?"
onChange = {(e) => setSearchword(e.target.value)}>
</input>
</div>
</form>
);
};
const Search= (props) => {
console.log(props)
const { updatevalue } = props;
return <SearchInput getinputvalue={updatevalue} history={props.history} />;
};
export default Search;
The listing page looks like this and takes the history object to make an api request to my db before rendering.
import React, { useState, Fragment } from 'react';
import Sticky from 'react-stickynode';
import Toolbar from 'components/UI/Toolbar/Toolbar';
import { Checkbox } from 'antd';
import CategotySearch from 'components/Search/CategorySearch/CategotySearch';
import { PostPlaceholder } from 'components/UI/ContentLoader/ContentLoader';
import SectionGrid from 'components/SectionGrid/SectionGrid';
import ListingMap from './ListingMap';
import FilterDrawer from 'components/Search/MobileSearchView';
import useWindowSize from 'library/hooks/useWindowSize';
import useDataApi from 'library/hooks/useDataApi';
import { SINGLE_POST_PAGE } from 'settings/constant';
import ListingWrapper, { PostsWrapper, ShowMapCheckbox } from './Listing.style';
export default function Listing({ location, history }) {
let url = 'http://127.0.0.1:5000/api/products'
if (history.search) {
url = url + history.search;
}
console.log(url)
const { width } = useWindowSize();
const [showMap, setShowMap] = useState(false);
const { data, loading, loadMoreData, total, limit } = useDataApi(url);
let columnWidth = [1 / 1, 1 / 2, 1 / 3, 1 / 4, 1 / 5];
if (showMap) {
columnWidth = [1 / 1, 1 / 2, 1 / 2, 1 / 2, 1 / 3];
}
const handleMapToggle = () => {
setShowMap((showMap) => !showMap);
};
return (
<ListingWrapper>
<Sticky top={82} innerZ={999} activeClass="isHeaderSticky">
<Toolbar
left={
width > 991 ? (
<CategotySearch history={history} location={location} />
) : (
<FilterDrawer history={history} location={location} />
)
}
right={
<ShowMapCheckbox>
<Checkbox defaultChecked={false} onChange={handleMapToggle}>
Show map
</Checkbox>
</ShowMapCheckbox>
}
/>
</Sticky>
<Fragment>
<PostsWrapper className={width > 767 && showMap ? 'col-12' : 'col-24'}>
<SectionGrid
link={SINGLE_POST_PAGE}
columnWidth={columnWidth}
data={data}
totalItem={total.length}
loading={loading}
limit={limit}
handleLoadMore={loadMoreData}
placeholder={<PostPlaceholder />}
/>
</PostsWrapper>
{showMap && <ListingMap />}
</Fragment>
</ListingWrapper>
);
}
I tried to pass down the history object so i do not use different history objects (like useHistory from "react-router-dom") but it didnt changed anything on that behaviour.
I Do assume this is because i try to do history.push(LISTING_PAGE) while i am already on this page. But as far i read, this should be irrelevant. What do you think?
EDIT:
My index.js lloks as follows:
const App = () => (
<ThemeProvider theme={theme}>
<>
<GlobalStyles />
<BrowserRouter>
<AuthProvider>
<Routes />
</AuthProvider>
</BrowserRouter>
</>
</ThemeProvider>
);

React re-renders the page when a key of the component is changed. So you can do this in your router. This will make sure the key is change every time a param updates, thus result in re-render of the component.
<Route
exact
path="/your-page/:param"
render={(props) => (
<YourComponent
key={props.match.params.prodId}
/>
)}
/>

You need to add a hidden Link element in your SearchInput component. also need to create a reference and pass it to the Link element to trigger the click action on it:
import React, {useRef} from 'react';
import {Link} from 'react-router-dom';
// rest of the codes ...
// inside of SearchInput component
const linkRef = useRef(null);
return (
<form className="search" onSubmit={submitHandler}>
<div className = "row">
<Link to={LISTING_POSTS_PAGE} className={{display: "none"}} ref={linkRef} />
// rest of the codes ...
Now, it's time to change the submitHandler method to trigger a click action on the Link element after submitting the form:
const submitHandler = (e) => {
e.preventDefault();
history.search= '?'+searchword;
linkRef.current.click() // ---> instead of using history.push()
}
Note: better solution may be available, like force page to re-render and so on but using a simple concept of Link will be helpful as I explained above.

Related

How to show form only for one element by id?

I want to show my edit form while clicking on some image, but it shows for all tasks, and I cant figure out how to do this only for one task
I tried to show edit form, changing boolean value "active" by useState. However, I dont understand how to create the function, that obtain id of task and show edit form only for this task.
Thanks in advance for reply
In App.js I have:
import {useState} from 'react'
import Tasks from "./components/Tasks";
function App() {
const [editActive, setEditActive] = useState(false)
const [tasks, setTasks] = useState( [
{
id: 1,
title: 'Task',
date: "12/03/2023 10:30",
status: false,
urgently: false,
}
....
])
....
return (
<div className="container">
<Tasks tasks={tasks} active={editActive} setActive={() => setEditActive(!editActive)} />
<div>
)
In Tasks.js:
import Task from "./Task"
const Tasks = ({tasks, active, setActive}) => {
return (
<div>
{tasks.map((task) => (
<Task key ={task.id} task={task} active={active} setActive={setActive}/>
))}
</div>
)
}
In Task.js:
import {useState} from 'react'
import {FaTimes, FaPencilAlt} from 'react-icons/fa'
import EditTask from './EditTask'
const Task = ({task, active, setActive}) => {
...
return (
<p>{task.date} <FaPencilAlt style={{cursor:'pointer'}} onClick={setActive}/></p>
{active && <EditTask onUpdate={onUpdate} task={task} setActive={setActive}/>}
)
Use conditional statement like
if(useState)
{
//put your function here to open task
}
You can pass the task id in setActive itself like setActive(task.id) and you can update task using useEffect hook in App.js to find the specific task on every modalActive change.
It was really simple. In Task.js you have to add this:
const Task = ({task, onUpdate}) => {
const [show, setShow] = useState(false);
const editTaskChanged = () =>{setShow(!show)}
....
return (
<p>{task.date} (<FaPencilAlt style={{cursor:'pointer'}} onClick={() =>
editTaskChanged()}/>)</p>
{show && <EditTask onUpdate={onUpdate} task={task} />}
)
After that, when you click on image in 'p' tag - you can see edit form

findDOMNode is deprecated in StrictMode. Warning - react-transition-group + react v17 + Javascript (Not Typescript)

I'm trying to get rid of a warning message in the project I'm working on.
index.js:1 Warning: findDOMNode is deprecated in StrictMode. findDOMNode was passed an instance of Transition which is inside StrictMode. Instead, add a ref directly to the element you want to reference. Learn more about using refs safely here: https://reactjs.org/link/strict-mode-find-node
at div
at Transition (http://localhost:3000/static/js/vendors~main.chunk.js:47483:30)
at CSSTransition (http://localhost:3000/static/js/vendors~main.chunk.js:46600:35)
at div
at TransitionGroup (http://localhost:3000/static/js/vendors~main.chunk.js:48052:30)
at Contacts (http://localhost:3000/static/js/main.chunk.js:1623:96)
at div
at div
at Home (http://localhost:3000/static/js/main.chunk.js:2549:88)
at AuthCheck (http://localhost:3000/static/js/main.chunk.js:2705:5)
at Routes (http://localhost:3000/static/js/vendors~main.chunk.js:45749:5)
at div
at Router (http://localhost:3000/static/js/vendors~main.chunk.js:45682:15)
at BrowserRouter (http://localhost:3000/static/js/vendors~main.chunk.js:45198:5)
at ContactState (http://localhost:3000/static/js/main.chunk.js:3743:85)
at AuthState (http://localhost:3000/static/js/main.chunk.js:3243:85)
at AlertState (http://localhost:3000/static/js/main.chunk.js:2844:85)
at App
The problematic code:
import React, { Fragment, useEffect } from 'react';
import { CSSTransition, TransitionGroup } from 'react-transition-group';
import { useContactContext } from '../../context/contact/contactContext';
import { useAuthtContext } from '../../context/auth/authContext';
import ContactItem from './ContactItem';
import Spinner from '../layout/Spinner';
const Contacts = () => {
const { contacts, filtered, getContacts, loading } = useContactContext();
const { isAuthenticated } = useAuthtContext();
useEffect(() => {
if (isAuthenticated) {
getContacts();
}
// eslint-disable-next-line
}, [isAuthenticated]);
if (!loading && contacts !== null && contacts.length === 0) {
return <h4>Please add a contact</h4>;
}
return (
<Fragment>
{contacts === null || loading ? (
<Spinner />
) : (
<TransitionGroup>
{(filtered || contacts).map((contact) => (
<CSSTransition timeout={1000} classNames="item" key={contact._id}>
<ContactItem contact={contact} />
</CSSTransition>
))}
</TransitionGroup>
)}
</Fragment>
);
};
export default Contacts;
I've spent a few hours looking for answers, but I feel like I'm running around in an endless loop.
To get rid of the warning, I need to use useRef hooks on each CSSTransition element, to connect it with (it's children?).
I can't use useRef() inside the render function of a component, so I defined a new component to display each TransitionItem:
...
const TransitionItem = ({ contact, ...props }) => {
const ref = useRef(null); // Had to use this ref to go around a warning
return (
<CSSTransition nodeRef={ref} timeout={1000} classNames="item" {...props}>
<div ref={ref}>
<ContactItem contact={contact} />
</div>
</CSSTransition>
);
};
return (
<Fragment>
{contacts === null || loading ? (
<Spinner />
) : (
<TransitionGroup>
{(filtered || contacts).map((contact) => (
<TransitionItem key={contact._id} contact={contact} />
))}
</TransitionGroup>
)}
</Fragment>
);
...
Now every time I try to click on a button, to remove an item from the list, I see a "flashing" effect, you can check out in this Sandbox: (Click on the red buttons to remove an item)
https://codesandbox.io/s/kind-feather-2psuz
The "flashing" problem only starts when I move the CSSTransition component into the new TransitionItem component, but I can't use useRef hooks on each item if I don't move it there.
Help pls! :)
PS:
Removing <React.StrictMode> from the index.js is not a solution to the root problem.
I have the same warning in my project and i can fix it with this solution, thank pixel-fixer !
Issue #668 on repo react-transition-group
From 4.4.0 release notes:
react-transition-group internally uses findDOMNode, which is
deprecated and produces warnings in Strict Mode, so now you can
optionally pass nodeRef to Transition and CSSTransition, it's a ref
object that should point to the transitioning child:
You can fix this like this
import React from "react"
import { CSSTransition } from "react-transition-group"
const MyComponent = () => {
const nodeRef = React.useRef(null)
return (
<CSSTransition nodeRef={nodeRef} in timeout={200} classNames="fade">
<div ref={nodeRef}>Fade</div>
</CSSTransition>
)
}
I hope it works for you, have a nice day !

Context API, Cannot retreive the data I am setting in seperate component

Working on a little project of mine but ran into an issue while working with the so called Context API. First time doing it as well.
The issue I am having is that I am unable to console.log the data I am setting. Now I am trying to make a "Add To Cart" button, and whenever the button is pressed. An object containing things such as name, price, color etc will be sent over to my ShoppingCart component.
I am able to set the value, whenever I press the button. And it appears in the console.log. Though, I am unable to console.log the value from ShoppingCart.
My files look like this:
./Contexts
./AddToCartContext.js
./Components
./Product
./ProductContent.js // Here the "add to cart" button is located.
./ShoppingCart
./ShoppingCart.js // Here I need the data from ProductContent.js
App.js
Here is my App.js code:
const [cartItems, setCartItems] = useState({});
console.log(cartItems); // The data is being logged here perfectly.
return (
<AddToCartContext.Provider value={{ cartItems, setCartItems }}>
<ThemeProvider theme={themeMode}>
{/* GlobalStyles skapas i ./themes.js */}
<GlobalStyles />
<Router>
<Route exact path="/cart">
<ShoppingCart theme={theme} toggleTheme={toggleTheme} />
</Route>
<Route exact path="/category/:type/:id/:productid">
<FetchAPI />
</Route>
// ...
</Router>
</ThemeProvider>
</AddToCartContext.Provider>
);
Here is my shoppingcart.js code:
import { AddToCartContext } from "../../Contexts/AddToCartContext";
const ShoppingCart = (props) => {
const { cartItems } = useContext(AddToCartContext);
console.log(cartItems); // This always result in an empty Object.
return (
<Fragment>
<TopNavigation />
<BottomNavigation theme={props.theme} toggleTheme={props.toggleTheme} />
<Cart />
<Footer />
</Fragment>
);
};
And here is the code in ProductContent.js (shorten as its pretty long):
import { AddToCartContext } from "../../Contexts/AddToCartContext";
const ProductContent = (props) => {
const { setCartItems } = useContext(AddToCartContext);
return (
<button
type="button"
onClick={() =>
setCartItems({ // This returns the object to App.js, but not Shoppingcart.js
name: props.name,
price: props.price,
color: props.mainImg?.colour,
img: props.mainImg?.url,
id: params.productid,
})
}
className={classes.add_to_cart}
>
<Cart />
add to cart
</button>
);
}
As mentioned, it's the first time I work with Context API. Am I missing anything?
EDIT:
Did some testing, and if I set the initial value on const [cartItems, setCartItems] = useState({}); to for example "test", then it is being rendered inside of the Cart page.
Nothing is logging inside of the cart upon the button press. But whenever the initial value is set to "test" it is loaded instantly.
Here is the code for creating the context (AddToCartContext.js):
import { createContext } from "react";
export const AddToCartContext = createContext({});
try without destructuring like this below
const cartItems = useContext(AddToCartContext);
console.log(cartItems);
and check if there is anything in that object

actions are dispatching every time i render a child component

I am new to react and kind of experimenting with the hooks and i am stuck in a problem where i am using useEffect to dispatch an action to redux store. so everything works fine but the problem i am facing is every time i render a child component in my main component it dispatch the action. Like there are cards that are basically child components and whenever i click on of them to show more details it dispatch the actions that are in parent components same if i close the component so my question how can i prevent that from happening and only render the items once. Let me know if you need any other code..
Parent Component
import React, { useEffect } from "react";
//ANIMATION AND STYLED
import styled from "styled-components";
import { motion, AnimatePresence, AnimateSharedLayout } from "framer-motion";
//REDUX and ROUTER
import {
AllPopularGame,
NextPage,
PrevPage,
} from "../Actions/popularGameActions";
import { useSelector, useDispatch } from "react-redux";
import { Link, useLocation, useHistory } from "react-router-dom";
//COMPONENTS
import Game from "./games";
import GameDetail from "./gameDetail";
const PopularGames = () => {
//GETTNG PATH
const Location = useLocation();
const History = useHistory();
const pathId = Location.pathname.split("/")[4];
//Redux store
const { allPopularGame, gameCount, currentPage, gameLoading } = useSelector(
(state) => state.popular
);
//No of pages
const totalPage = Math.ceil(gameCount / 36);
//SCROLL TO TOP
useEffect(() => {
window.scrollTo(0, 0);
}, [currentPage]);
//Handlers
const PrevHandler = () => {
if (currentPage <= 1) {
return;
} else {
dispatch(PrevPage());
History.push(`/popular/games?page=${currentPage - 1}`);
}
};
const NextHandler = () => {
if (currentPage >= totalPage) {
console.log("Hello");
return;
} else {
dispatch(NextPage());
History.push(`/popular/games?page=${currentPage + 1}`);
}
};
//Fetch all popular games
const dispatch = useDispatch();
useEffect(() => {
async function fetchGames(page) {
const games = dispatch(AllPopularGame(page));
return games;
}
fetchGames(currentPage);
}, [dispatch, currentPage]);
// {`${currentPage} /popular/games/${popularGames.id}`}
return (
<Popular>
<h2>Popular Games </h2>
<AnimateSharedLayout type="crossfade">
<AnimatePresence>
{pathId && <GameDetail pathId={pathId} curPage={currentPage} />} //child component
</AnimatePresence>
{gameLoading ? (
<h2>Loading</h2>
) : (
<Games>
{allPopularGame.map((popularGames) => (
<Link
to={`/popular/games/${currentPage}/${popularGames.id}`}
key={popularGames.id}
>
<Game
name={popularGames.name}
img={popularGames.background_image}
rating={popularGames.rating}
id={popularGames.id}
key={popularGames.id}
released={popularGames.released}
/>
</Link>
))}
</Games>
)}
</AnimateSharedLayout>
<Page>
<Button onClick={PrevHandler}>
<span>Prev</span>
</Button>
<p>{currentPage}</p>
<Button onClick={NextHandler}>
<span>Next</span>
</Button>
</Page>
</Popular>
);
};
Github repo
Current code
Thanks for sharing the repository! It's quite clear now. You're using a link and re-routing the page - so although the components are the same and there isn't a re-paint on the UI, the entire page still mounts again. That's why your useEffect gets triggered everytime you click on a card (and even when you close one!).
Your UI state is managed by the path in the URL - this is really bad practice - especially considering that you aren't making any API calls to fetch data based on the URL.
Here's what I would suggest -
Replace the pathID variable that you are currently reading from the URL with a useState hook:
const [activeGameID, setActiveGameID]=useState(null);
Replace the Link component that you use to wrap the Game with a regular div and pass it an onClick handler to setActiveGameID:
<Games>
{popular.map((popularGames) => (
<div
onClick={() => setActiveGameID(popularGames.id)}
key={popularGames.id}
>
<Game
name={popularGames.name}
img={popularGames.background_image}
rating={popularGames.rating}
id={popularGames.id}
key={popularGames.id}
released={popularGames.released}
/>
</div>
))}
</Games>```
I think the problem you may be facing is that your animations are dependent on URL changes - in that case I would urge you to use some other way to animate your cards.

React render list only when data source changes

Basically I have a modal with a state in the parent component and I have a component that renders a list. When I open the modal, I dont want the list to re render every time because there can be hundreds of items in the list its too expensive. I only want the list to render when the dataSource prop changes.
I also want to try to avoid using useMemo if possible. Im thinking maybe move the modal to a different container, im not sure.
If someone can please help it would be much appreciated. Here is the link to sandbox: https://codesandbox.io/s/rerender-reactmemo-rz6ss?file=/src/App.js
Since you said you want to avoid React.memo, I think the best approach would be to move the <Modal /> component to another "module"
export default function App() {
return (
<>
<Another list={list} />
<List dataSource={list} />
</>
);
}
And inside <Another /> component you would have you <Modal />:
import React, { useState } from "react";
import { Modal } from "antd";
const Another = ({ list }) => {
const [showModal, setShowModal] = useState(false);
return (
<div>
<Modal
visible={showModal}
onCancel={() => setShowModal(false)}
onOk={() => {
list.push({ name: "drink" });
setShowModal(false);
}}
/>
<button onClick={() => setShowModal(true)}>Show Modal</button>
</div>
)
}
export default Another
Now the list don't rerender when you open the Modal
You can use React.memo, for more information about it please check reactmemo
const List = React.memo(({ dataSource, loading }) => {
console.log("render list");
return (
<div>
{dataSource.map((i) => {
return <div>{i.name}</div>;
})}
</div>
);
});
sandbox here

Categories

Resources