React - Browser's Back button doesn't work - reactjs

In the following component, if I click on any of the two buttons, the URL in the address-bar gets changed.
In the list view if you click on the button Details the page get rendered and shows that particular item and the URL in the address bar get changed too.
And in the user view if you click on the "Back to overview" button, the page renders back to the list view and the URL gets changed again.
import React, { useState, useEffect, Fragment } from 'react'
import axios from 'axios'
const UserList = ({ id, setID }) => {
const [resources, setResources] = useState([])
const fetchResource = async () => {
const response = await axios.get(
'https://api.randomuser.me'
)
setResources(response.data.results)
}
useEffect(() => {
fetchResource()
}, [])
const renderItem = (item, userId) => {
const setURL = (e) => {
window.history.pushState(null, null, '/' + e)
setID(item.login.uuid)
}
const clearURL = (e) => {
window.history.back()
setID(null)
}
return (
<Fragment key={item.login.uuid}>
{userId ? (
// User view
<div>
<img src={item.picture.large} />
<h2>
{item.name.first}
</h2>
<p>
{item.phone}
<br />
{item.email}
</p>
<button onClick={() => clearURL('/')}>
Back to overview
</button>
</div>
) : (
// List view
<li>
<img src={item.picture.large} />
<h2>
{item.name.first}
</h2>
<button onClick={() => setURL(item.login.uuid)}>
Details
</button>
</li>
)}
</Fragment>
)
}
const user = resources.find(user => user.login.uuid === id)
if (user) {
// User view
return <div>{renderItem(user, true)}</div>
} else {
// List view
return (
<>
<ul>
{resources.map(user => renderItem(user, false))}
</ul>
</>
)
}
}
export default UserList
Everything is working fine.
However, the problem with this solution is that on user view, I cannot use the browsers back button to go back to the list view page.
Is there any way I can change this without using React Route?

So what I believe is happening is you are over-writing the 'history' of the browser. The 'history' is pretty much just a stack of paths you've been and when you click the back button it just pops the current path off the stack. I think that when your using 'window.history.pushState(null, null, '/' + e)' its setting the history = to null so there is nothing to go back to. I would recommend using react-router and just pushing a path ie. router.push('/whatever'). There are a bunch of ways to interact with the history through react without using the native history object. If you want to use the native history object https://developer.mozilla.org/en-US/docs/Web/API/History_API
Edit - I apologize your not overwritting the history, but I do still believe that the error is coming from passing a 'null' value as the 'state' param for the pushState method

Related

How to write test for a button inside a list tag? Unable to get the button element inside a ui tag?

checkResult is a helper function which is imported in my component.jsx
component.jsx
return(
<ul>
{options.map((option) => {
return (
<li key={option.value}>
<button
data-testid="unlock-btn"
onClick={() => {
checkResult()
? lunch(option.value)
: showError();
}}
>
{option.label}
</button>
</li>
);
})}
</ul>;
)
my test
import * as helper from "../helpers/checkResult";
it("fires action when lunch is clicked", async () => {
const spy = jest.spyOn(helper, 'checkResult');
let component;
await act(async()=>{
component = <component /> ;
})
await expect(screen.queryByTestId("unlock-btn"));
fireEvent.click(screen.queryByTestId("unlock-btn"));
expect(spy).toHaveBeenCalled();
});
this is the error i'm getting
Unable to fire a "click" event - please provide a DOM element.
i have also provided my getComponent Method above
You're not providing options to the component so it has nothing to render. You're also using a map to render a list of items all of which have the same id. You should do something like
map((option, index) => {
return (
<li key={option.value}>
<button
data-testid=`unlock-btn-${index}`
This way you can target each individual option by ID in your test.
Edit: Your fireEvent is not defined in your example either.
The right way would be using the aria-label and attributes to be able to select those buttons without the need of data-testid.
<button
onClick={() => { checkResult() ? lunch(option.value): showError();}}
name={option.label} // assuming the labels are unique
>
{option.label}
</button>
then:
import React from 'react';
import { render, screen, fireEvent } from '#testing-library/react';
it('Should do some test', ()=>{
render(<MyComponent/>)
const button = screen.getByRole('button', {name: "some-label"})
fireEvent.click(button)
expect(....).toBe(...)
}

react-toastify popup showing 2 times

Website error visual on chrome
I create a react website. On this website, I create a social login icon using firebase-hooks. But when I click on the social login button the pop-up shows. But after closing that pop-up I use react toastify to show the error. But always It's showing twice. and can't fix this problem
const SocialLogin = () => {
const navigate = useNavigate();
const location = useLocation();
const from = location.state?.from?.pathname || "/";
const [signInWithFacebook, facebookUser, facebookLoading, facebookError] =
useSignInWithFacebook(auth);
facebookError && toast.error(facebookError.message);
const [token] = useToken(facebookUser);
token && navigate(from, { replace: true });
return (
<div>
<div className="or">
<div></div>
OR
<div></div>
</div>
<div className="social-logins">
<p>{facebookLoading && `Loadin Please Wait`}</p>
<div className="social-btn" onClick={() => signInWithFacebook()}>
<SiFacebook />
<span>Facebook</span>
</div>
</div>
<ToastContainer pauseOnHover />
</div>
);
};
toast message will be appeared on every re render , you need to call toast when you get error message from fb , you need to call toast inside of useEffect, something like this
React.useEffect(() => {
if (facebookError.message) {
toast.error(facebookError.message);
}
}, [facebookError.message])

How to render Video Page when the button is clicked before taking the next action in React.js

how can I show a video when the button is clicked and then it performs an
action , so step by step:
user lands to the main page
user clicks on the button that should take him/her to home page
I used useNavigate to change the route of the page to /home, but before doing that, I want the user to see short video.
so far this is what I came up with, but it doesnt work
const Main = (props) => {
let navigate = useNavigate();
const playVideo = () => {
return (
<div>
<video src={video}></video>
</div>
)
}
const handleClickHome = async (event) => {
event.preventDefault();
await playVideo()
return (
navigate("/home")
)
}
return (
<div className={classes.main} >
<button onClick={handleClickHome} className={classes.button}> <h2> LET ME IN </h2> </button>
</div >
);
}
export default Main ```
You can show your video by clicking the button and use under below event for navigation.
document.getElementById('videoId').addEventListener('ended',myHandler,false);
function myHandler(e) {
// do navigation
}

How do I idiomatically turn a text component into an input component on click?

I am creating a todo app in React that is basically just a list of items, grouped by category. I want to add functionality such that when I click a single to do(which is a paragraph), it brings up an input with the current text that I can edit and save. How can I do that without manually editing the DOM?
Code:
A single todo item:
import React from 'react';
const Item = props => {
return (
<div
className={`item${props.item.purchased ? ' purchased' : ''}`}
onClick={() => props.toggleItem(props.item.id)}
>
<p>{props.item.name}</p>
</div>
);
};
export default Item;
I want to change the toggle to be a radio button and onClick to edit the todo.
sample image of todos
First of all, you will need a prop that updates item.name (this will be needed when you will edit the input)
You didn't explained well how you want it to work, so I made an example where you click on the text to edit it to a text input and also have a button to save the edit.
const Item = props => {
const [isEditing, setIsEditing] = useState(false);
return isEditing ? (
<div>
<input
value={props.item.name}
onChange={e => props.setItemName(e.target.value)}
/>
<button onClick={() => setIsEditing(false)}>Stop Editing</button>
</div>
) : (
<div
className={`item${props.item.purchased ? " purchased" : ""}`}
onClick={() => setIsEditing(true)}
>
<p>{props.item.name}</p>
</div>
);
};
I also created a working codesanbox with the behavior you want.
You will have to maintain a state to change between the TODO Item & TODO Input. Since you are using functional component, you can use useState hook from react to maintain the state as shown below
import React, { useState } from 'react';
const Item = props => {
const [isEditing, setIsEditing] = useState(false);
if (isEditing) {
return (
<div className={`item${props.item.purchased ? ' purchased' : ''}`}>
<input type="text/radio" value={props.item.name}>
</div>
);
} else {
return (
<div
className={`item${props.item.purchased ? ' purchased' : ''}`}
onClick={() => props.toggleItem(props.item.id)}
>
<p>{props.item.name}</p>
</div>
);
}
};
export default Item;
You might need to change the above a bit based on your application structure but this is what you need to follow.

Rendering large amount of items prevents other functions to work before render completes

I am trying to find out how to prevent rendering a large amount of data freezing other functionalities in a React component.
In the code, I have a button called Render, which renders 30000 items and another button, Reset, that removes them.
Once I click Render, I am unable to click the other button, Reset because the component is busy rendering 30000 items for the time being.
I want to be able to click Reset button while the component is trying to render the items. Please help me with ways to resolve this issue.
const ItemsComponent = () => {
const [displayItems, setDisplayItems] = useState(false);
const items = Array.from(Array(30000)).map((item, index) => {
return (
<li key={index}>
Item {index + 1}
</li>
)
});
const renderItems = () => { setDisplayItems(true) }
const resetItems = () => { setDisplayItems(false) }
return (
<div>
<button onClick={renderItems}>Render</button>
<button onClick={resetItems}>Reset</button>
<ul>
{ displayItems ? items : null }
</ul>
</div>
);
}

Resources