how to move focus to an item in react js? - javascript

I have implemented a list with infinite scroll in my demo application.on click of any row it will go to detail screen. It is working fine.
**I am facing a issue to focus the last selected row ** In other words
Run the application .it load first 20 items.Scroll to bottom to load more 20 items.
Then click any item let say 33rd row . it will display 33 in detail page.
Now click on back button it show focus on 0 or first row. I want to move focus to 33 row .or Move the scroll position to 33 position.
I use useContext api to store the items(all rows/data till last scroll) and selected item (selected index).
here is my code
https://codesandbox.io/s/dank-cdn-2osdg?file=/src/useInfinitescroll.js
import React from "react";
import { withRouter } from "react-router-dom";
import { useListState } from "./context";
function Detail({ location, history }) {
const state = useListState();
console.log(state);
return (
<div className="App">
<button
onClick={() => {
history.replace({
pathname: "/"
});
}}
>
back
</button>
<h2>{location.state.key}</h2>
<h1>detaiils</h1>
</div>
);
}
export default withRouter(Detail);
any update?

Use this before you redirect to the detailed page and store it in a state.
let position = document.documentElement.scrollTop
This will give your current position on the page. Once you are back to list view use
window.scrollTo(0, position)
to go back to where you were initially.

You could have a single source of truth for your element position. As an example I put the state of the position on the Router component since this seems to be the parent of the App component & Details.
const Router = () => {
const [previousClickedItemPos, setPreviousClickedItemPos] = useState(0);
/* import useLocation so that useEffect can be invoked on-route-change */
const location = useLocation();
/* basically pass this to the list items so you can update position on-click */
function handleClickList(element) {
setPreviousClickedItemPos(element);
}
useEffect(() => {
/* scroll to the element previously selected */
window.scrollTo(0, previousClickedItemPos);
}, [previousClickedItemPos, location]);
...
On your InfiniteList component:
<ListItem
onClick={e => {
dispatch({
type: "set",
payload: items
});
handleClickList(e.target.offsetTop); /* update position state */
goDetailScreen(item);
}}
key={item.key}
>
{item.value}
</ListItem>
CodeSandBox: https://codesandbox.io/s/modern-breeze-nwtln?file=/src/router.js

Related

How would I achieve this scroll background colour change effect?

Basically, assume I have 10 sections. Each have a different colour assigned to them for background colour.
When the user scrolls down from sections 1 through 10, I would like the tag background colour to change accordingly, depending which section is on screen.
Assuming the height of the viewport is 1000px, I would like the function to find out which section is currently at 800px out of 1000px, so the bottom 20%, then find the background color of that section in the bottom 20% and apply it to the tag until the user either scrolls to the next section, or scrolls up and another component takes over the background colour.
I have tried to use IntersectionObservor for this but I don't think it is the best approach for what I want.
Currently, my setup is, I am rendering multiple components after each other, each of them has a data attribute of "data-background={background}"
Then, the observer loops through, adds them all to the observer, and watches to find which one is on screen, but it isn't working completely for what I need.
Is there an easier way to achieve what I am looking for?
Here is the code I have so far
import Page from "../components/common/Page";
import Hero from "../components/molecules/Hero";
import TechStack from "#/components/organisms/TechStack";
import { useEffect } from "react";
const Home = () => {
useEffect(() => {
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
console.log("entry", entry);
if (entry.isIntersecting) {
document.body.style.backgroundColor =
entry.target.dataset.background;
}
});
},
{ threshold: [0.20] }
);
// create an array of all the components to be watched
const components = [...document.querySelectorAll("[data-background]")];
components.forEach((component) => {
observer.observe(component);
});
}, []);
return (
<Page seo={{ title: "Starter Kit" }}>
<Hero />
<TechStack background="white"/>
<TechStack background="grey" />
<TechStack background="blue"/>
<TechStack background="green"/>
<TechStack background="grey"/>
<TechStack background="white"/>
</Page>
);
};
export default Home;
You can dynamically add the element to the observer when it mounted, like this
<div ref={(r) => r && observer.observe(r)} />
Here is the example: https://codesandbox.io/s/sleepy-margulis-1f7hz7

react router: how to get hidden parameters attached to url when doing history.push(url)?

I have the following code for a board component that is the main display on a website page.
A modal pop up window is defined such that when the url is matched with history.push(url) somewhere else in the code base, a corresponding modal window shall pop up and display on top of the current page. Clicking different buttons will open the modal window for corresponding items.
I'm stuck with the following situation:
Say there are two ways to trigger the same modal window of an item to open:
Clicking a button will open the modal window for the item;
drap and drop the button to some box will also open the modal window for the item.
The code is as below:
import React, { useState, Fragment } from "react";
import { Route, useRouteMatch, useHistory, Switch } from 'react-router-dom';
const Board = () => {
const match = useRouteMatch();
const history = useHistory();
... other code ...
return (
<Fragment>
... some other components ...
<Route
path={`${match.path}/items/:itemId`}
render={routeProps => (
<Modal
isOpen
onClose={()=>history.push(match.url)}
renderContent={modal => (
<PopupWindow
itemId={routeProps.match.params.itemId}
test={routeProps.match.state.trigger}
/>
)}
/>
)}
/>
</Fragment>
);
}
In other scripts, I have defined button components like this:
<ItemLink
to={{
pathname: `${match.url}/items/${item.id}`,
state: { trigger: 'fromClick'}
}}
ref={provided.innerRef}
>
<Item>
<Title>{item.title}</Title>
<Bottom>
<div>
... some code ...
</div>
</Bottom>
</Item>
</ItemLink>
such that clicking different buttons will open the modal window for corresponding items.
and for method 2, drap and drop the button to some box will also open the modal window for the item:
const match = useRouteMatch();
const history = useHistory();
const handleItemDrop = () => {
... some code ...
history.push({
pathname: `${match.url}/items/${itemId}`,
state: {trigger: 'fromClick'}
})
}
such that when item is dropped, history.push(url) is triggered and the modal window should pop up.
I'm passing a hidden parameter state.trigger to record how the url is triggered.
However, I got the following error:
TypeError: Cannot read property 'trigger' of undefined
renderContent
src/components/Board.js:99
96 | renderContent={modal => (
97 | <PopupWindow
98 | itemId={routeProps.match.params.itemId}
> 99 | test={routeProps.match.state.trigger}
| ^ 100
How can I get the hidden parameter?
I got the answer myself 5 minutes after posting this question:
change routeProps.match.state.trigger
to
routeProps.location.state.trigger

React: useState not preserving updated state upon click

Update: I was truly trying to reinvent the wheel here, the code below works if you use it in conjunction with React's Link or NavLink instead of anchor tags, it has built-in listening functionality that will keep track of the page you are currently on and pass along the updated state accordingly as your route changes to a different page.Thank you to everyone that chimed in and pointed me in the right direction!
I'm still fresh off the block with React, especially with hooks, but what I'm trying to accomplish is to trigger the 'active' tab class of my navbar elements through conditional rendering and managing state with useState.
However, when I call 'setActiveTabIdx' upon click, I can't tell if it's not updating state at all, or if it is and is resetting to the default value upon re-render. I was trying to use my dev tools to monitor upon click but it's happening too fast for me to say one way or the other. I've messed around a fair bit at this point trying a number of different things if anyone would be willing to take a look, thanks!
const NavBar = () => {
const [activeTabIdx, setActiveTabIdx] = useState(0)
const navItems = ['About', 'Services', 'Oils', 'New Patients', 'Courses', 'Recommendations', 'Contact' ]
const renderedItems = navItems.map((nav, idx) => {
const active = idx === activeTabIdx ? 'active' : ''
return (
<a
onClick={() => setActiveTabIdx(idx)}
href={`${nav.split(' ').join('').toLowerCase()}`}
className={`${active} item`}
key={idx}
>{nav}</a>
)
})
return (
<div className="ui tabular menu">
{renderedItems}
</div>
);
};
export default NavBar;
You are trying to change state (which is done) and then redirect user to other page (which is also done). The state resets after redirection.
It seems it is resetting, I added this block to check:
const NavBar = () => {
const [activeTabIdx, setActiveTabIdx] = useState(0)
const navItems = ['About', 'Services', 'Oils', 'New Patients', 'Courses', 'Recommendations', 'Contact' ]
// --------- Start block ---------
useEffect(() => {
console.log(`current state: ${activeTabIdx}`);
}, [activeTabIdx]);
// --------- End block ---------
const renderedItems = navItems.map((nav, idx) => {
const active = idx === activeTabIdx ? 'active' : ''
return (
<a
onClick={() => setActiveTabIdx(idx)}
href={`${nav.split(' ').join('').toLowerCase()}`}
className={`${active} item`}
key={idx}
>{nav}</a>
)
})
return (
<div className="ui tabular menu">
{renderedItems}
</div>
);
};
export default NavBar;
And to check codesandbox

Sliders not Removing Properly

Here is all the code:
import React, { Component } from "react";
import "./App.css";
import Slider from "#material-ui/core/Slider";
import Typography from "#material-ui/core/Typography";
class App extends Component {
state = {
users: [],
};
componentDidMount() {
fetch("/users").then((response) =>
response.json().then((data) => {
this.setState({ users: data.users });
})
);
}
removeSlider(user) {
const users = [...this.state.users];
users.splice(users.indexOf(user), 1);
this.setState({ users: users });
}
render() {
return (
<div className="App">
{this.state.users.map((user) => (
<div className="slider" id="di">
<Typography id="range-slider" gutterBottom>
<button className="btn" onClick={() => this.removeSlider(user)}>
{user.first_name[0].toUpperCase() + user.first_name.slice(1)}
</button>
</Typography>
<Slider
orientation="vertical"
defaultValue={[0, 50]}
aria-labelledby="vertical-slider"
/>
</div>
))}
</div>
);
}
}
export default App;
The users come from another server. The in the typography each user has a button that essentially removes them from the list of users (handled by the removeSlider handler).
The problem here is that when I do this the slider that correspondes with the end of the list gets removed instead of the one I want to delete.
So in the pictures I go to delete Jordan, but Imagine's slider gets removed. Jordan was in fact removed from the array of users though.
Any help would be appreciated.
When a user is removed, because of a state change, React re-renders the page, and inside the map React will draw 5 sliders, which it will associate with the first 5 sliders as you haven't added keys to them.
So a first step would be to add keys to the sliders in map, then the re-rendering should keep and re-use the correct old objects and not re-draw or mis-align them.
Also, you might want to store the sliders' values in the state, making them "controlled components" (set their value from state, and handle their change events to update the state) so that they don't lose their info between renders.

Access props/attributes from child component with hooks

I'm trying to create a feature to easily hide/show all items (subcomponents). By using useState I am able to set whether or not all items are hidden/shown. By using useEffect I am able to toggle items that are hidden/shown. I'm having issues accessing the props in the subcomponent to determine whether or not a an item has already been expanded. I wish I could explain this better, but hopefully this coding example will paint a better picture.
index.js
import React, { useState } from "react";
import ReactDOM from "react-dom";
import "semantic-ui-css/semantic.min.css";
import { Button } from "semantic-ui-react";
import Item from "./Item";
const Services = props => {
const [allExpanded, setAllExpanded] = useState(false);
return (
<>
<p>
<Button onClick={() => setAllExpanded(false)} content="Hide all" />
<Button onClick={() => setAllExpanded(true)} content="Show all" />
</p>
<p>
<Item expanded={allExpanded} />
<Item expanded={allExpanded} />
<Item expanded={allExpanded} />
</p>
</>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<Services />, rootElement);
Item.js
import React, { useState, useEffect } from "react";
import { Accordion } from "semantic-ui-react";
const Item = props => {
const [expanded, setExpanded] = useState(props.expanded);
useEffect(() => {
setExpanded(props.expanded);
}, [props.expanded]);
return (
<Accordion styled>
<Accordion.Title
onClick={() => {
setExpanded(!expanded);
}}
>
<p>{expanded ? "- Hide Item" : "+ Show Item"}</p>
</Accordion.Title>
<Accordion.Content active={expanded}>Lorem ipsum...</Accordion.Content>
</Accordion>
);
};
export default Item;
CodeSandbox
To replicate my current bug, click any "+ Show Item", then click "Hide All". It will not hide everything, however clicking "Show All", then "Hide All" will hide everything.
You're facing this issue because your parent component actually has three possible states:
All expanded
All collapsed
Neither all expanded or collapsed
To reflect the third state, you could use null/undefined (and pass the setter down into your children components).
Updated example here: https://codesandbox.io/s/competent-villani-i6ggh
Since you are handling the expanded state of your accordions on the top level, I suggest you just pass down the expanded state and the 'toggler' to your items. index.js will handle the logic and your Item component will be presentational.
Here's a fork of your CodeSandbox.
It doesn't look great and probably the item state and toggling can (and probably should) be moved elsewhere (for example a separate reducer with the usage of the useReducer hook)
If you are planning to create these components dynamically, IMO this is the easiest way to go.
If you still want to go your way, you can refactor your Item to a class component and use Refs to get their current state, however I not recommend this approach.
Hope this helps!
Here's a codeandsandbox, forked from yours:
https://codesandbox.io/s/competent-wildflower-n0hb8
I changed it so that instead of having something like this:
let [allExpanded, setAllExpanded] = useState(true)
You have something like this:
let [whichExpanded, setWhichExpanded] = useState({0: true, 1:true, 2: true})
Then, on for your callback to expand/collapse all buttons, you have this:
<button onClick=()=>{
let newState = {}
for(let order in whichEpanded){
newState[order] = false //change every key to false
}
setAllExpanded(newState)
}> hide all</button>
Then, I passed down an "order" prop to your items. The "order" prop is used as an argument to a callback function that I pass down, so when you click on each item, it updates the whichExpanded state, to toggle the visibility of just that one item.
// pass this to eac of the items:
const setIndividualItemExpanded = order => {
let newItemsExpandedState = { ...itemsExpanded };
newItemsExpandedState[order] = !newItemsExpandedState[order];
setItemsExpanded(newItemsExpandedState);
};
Each item component:
<Item
expanded={itemsExpanded[0]} //is reading from the state
order={0}
setExpanded={setIndividualItemExpanded}
/>
Then, you can remove the useState from the rendered component and just update with the "setExpanded" prop
(See complete code in codesandbox pasted at top)

Categories

Resources