Scrolling to a particular item - javascript

In my React component, I'm displaying a list of items -- each in its own DIV element with a unique id i.e. <div id="abc-123">.
I'm also using react-perfect-scrollbar to make the whole thing nicer looking.
I keep a variable in my reducer named activeElementId and when the value of activeElementId changes, I want to automatically scroll to that item on the screen.
Setting the activeElementId is the easy part but I'm not sure how to scroll to that element and would appreciate some pointers.
This is the parent component that contains the ListComponent.
class MyComponent extends Component {
constructor(props) {
super(props);
}
render() {
return(
<div>
{this.props.items.length > 0
?
<PerfectScrollBar>
<ListComponent items={this.props.items} />
</PerfectScrollBar>
: null}
</div>
);
}
}
My ListComponent is a presentational component:
const ListComponent = ({ items }) => {
return(
<ul className="pretty-list">
{items.map(item => <ItemComponents item={item} />)}
</ul>
);
}
export default ListComponent;
And the ItemComponent is a presentational component as well:
const ItemComponent = ({ Item }) => {
return(
<li>
<div id={item.id}>
{item.someProperty}
</div>
</li>
);
}
export default ItemComponent;
I really like the idea of keeping ListComponent and ItemComponent separate and as presentational components as that helps keep the code simpler and easier to manage. Not sure if that would make it difficult to implement the auto scroll logic though.

The library you use has a method called setScrollTop. You can use it with getBoundingClientRect. To use getBoundingClientRect you need to have the dom-element. You didn't give any code about how you are setting or getting the active element but I'll try to give you an example. Maybe it will help you to implement on your code.
Example
class MyComponent extends Component {
constructor(props) {
super(props);
}
_onListItemChange = (itemsPosition) => {
this.scrollbar.setScrollTop(itemsPosition.top);
}
render() {
return (
<div>
{this.props.items.length > 0 ?
<PerfectScrollBar ref={(scrollbar) => { this.scrollbar = scrollbar; }}>
<ListComponent
items={this.props.items}
onListItemChange={this._onListItemChange} />
</PerfectScrollBar>
: null}
</div>
);
}
const ListComponent = ({ items, onListItemChange }) => {
return(
<ul className="pretty-list">
{items.map(item => (
<ItemComponents
item={item}
onListItemClick={onListItemChange} />
))}
</ul>
);
}
export default ListComponent;
import { render, findDOMNode } from 'react-dom';
class ListItem extends React.Component {
_onClick = () => {
let domElement = findDOMNode(this.item);
this.props.onListItemClick(domElement.getBoundingClientRect());
}
render() {
const { item } = this.props;
return(
<li>
<div id={item.id} ref={(item) => { this.item = item; }} onClick={this._onClick}>
{item.someProperty}
</div>
</li>
);
}
}

Related

Passing props to nested child component

Here's my structure :
Main.js (Parent)
MainContainer.js
|
|_ Article.js
|
|__ Comments.js
Now i want to set click handler on comment component (recursive component) and dispatch an action.
here's my code on comment.js
class Comment extends Component {
deleteComment = (id) => {
this.props.handleDelete(id)
}
render() {
var comment = this.props.comment
return (
<div className={styles.commentsWrapper}>
<ul>
<li>
<div className={styles.commentsName}>
<a onClick={() => this.deleteComment(comment.id)} className={styles.commentsNameRight}>
</a>
</div>
<p>{comment.body}</p>
{comment.children.length > 0 && comment.children.map(function(child) {
return <Comment comment={child} key={child.id}/>
})}
</li>
</ul>
</div>
);
}
}
export default Comment;
and Article.js :
class Article extends Component {
handleDeleteComment = (id) => {
this.props.deleteComment(id)
}
render() {
return (
<article className={styles.articleItem}>
{this.props.comments.map(item =>
<Comment handleDelete={this.handleDeleteComment} comment={item} key={item.id}/>)}
</article>
);
}
}
export default Article;
And the Main.js
class Main extends Component {
deleteComment = (id) => {
this.props.deleteCommentRequest(id)
}
render() {
return (
<div className="">
<Header />
<section className="container">
<div>
{
!this.props.articles.loading && this.props.articles.articles? (
<div>
{this.props.articles.articles.map(item =>
<Article
bodytext={item.selftext}
key={item.id}
comments={item.finalComments}
deleteComment={this.deleteComment}
/>)}
</div>
) : (
<div className={styles.loading}> <Spin /> </div>
)
}
</div>
</section>
</div>
);
}
}
export default Main;
so what i did here is: pass deleteComment as props from main to article and pass again handleDelete from article to comment.
not sure if it's a good way of doing this ?
Thanks in advance
Nothing wrong with this pattern for 2 - 3 depth of components, as that is how data should flow from children to ancestors. But if your application is getting heavier with several layers, consider a different state management such as redux where a global state is maintained and any component can subscribe to it and dispatch actions. More on that here.
Alternatively you can also achieve the same with React Hooks with useContext where you can set the context and any child component can subscribe to it. Example:
const MyContext = React.createContext();
export default function App({ children }) {
const [items, setItems] = React.useState([]);
return (
<MyContext.Provider value={{ items, setItems }}>
{children}
</MyContext.Provider>
);
}
export { MyContext };
Now in any child at any level of depth as long as it is within App component's children, you can do this:
import {MyContext} from './filename';
function TodoItem() {
const { items, setItems } = React.useContext(MyContext);
return (
<div onClick={() => setItems(1)}>
</div>
);
}
you can use context API to have the props in the wrapper and easily accessible from child component.
there is a great tutorial from wesbos on youtube
class App extends Component {
render() {
return (
<MyProvider>
<div>
<p>I am the app</p>
<Family />
</div>
</MyProvider>
);
}
}
class MyProvider extends Component {
state = {
name: 'Wes',
age: 100,
cool: true
}
render() {
return (
<MyContext.Provider value={{
state: this.state,
growAYearOlder: () => this.setState({
age: this.state.age + 1
})
}}>
{this.props.children}
</MyContext.Provider>
)
}
}

Easy communication of image between siblings

I'm new to ReactJS and I would like to communicate between my components.
When I click an image in my "ChildA" I want to update the correct item image in my "ChildB" (type attribute in ChildA can only be "itemone", "itemtwo", "itemthree"
Here is what it looks like
Parent.js
export default class Parent extends Component {
render() {
return (
<div className="mainapp" id="app">
<ChildA/>
<ChildB/>
</div>
);
}
}
if (document.getElementById('page')) {
ReactDOM.render(<Builder />, document.getElementById('page'));
}
ChildA.js
render() {
return _.map(this.state.eq, ecu => {
return (
<img src="../images/misc/ec.png" type={ecu.type_eq} onClick={() => this.changeImage(ecu.img)}/>
);
});
}
ChildB.js
export default class CharacterForm extends Component {
constructor(props) {
super(props);
this.state = {
items: [
{ name: "itemone" image: "defaultone.png"},
{ name: "itemtwo" image: "defaulttwo.png"},
{ name: "itemthree" image: "defaultthree.png"},
]
};
}
render() {
return (
<div className="items-column">
{this.state.items.map(item => (<FrameCharacter key={item.name} item={item} />))}
</div>
);
}
}
I can retrieve the image on my onClick handler in my ChildA but I don't know how to give it to my ChildB. Any hints are welcomed, thanks you!
What you need is for Parent to pass an event handler down to ChildA which ChildA will call when one of the images is clicked. The event handler will call setState in Parent to update its state with the given value, and then Parent will pass the value down to ChildB in its render method.
You can see this working in the below example. Since I don't have any actual images to work with—and to keep it simple—I've used <button>s instead, but the principle is the same.
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {
clickedItem: 'none',
};
}
render() {
return (
<div>
<ChildA onClick={this.handleChildClick}/>
<ChildB clickedItem={this.state.clickedItem}/>
</div>
);
}
handleChildClick = clickedItem => {
this.setState({ clickedItem });
}
}
const items = ['item1', 'item2', 'item3'];
const ChildA = ({ onClick }) => (
<div>
{items.map(name => (
<button key={name} type="button" onClick={() => onClick(name)}>
{name}
</button>
))}
</div>
);
const ChildB = ({clickedItem}) => (
<p>Clicked item: {clickedItem}</p>
);
ReactDOM.render(<Parent/>, document.querySelector('div'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.development.js"></script>
<div></div>

React click on item to show details

Iam new to React and I'm trying to interact with the swapi API.
I want to get the list of films (movie titles list) and when I click on a title to show the opening_crawl from the json object.
I managed to get the film titles in an array. I don't know how to proceed from here.
Here is my code:
class StarWarsApp extends React.Component {
render() {
const title = "Star Wars";
const subtitle = "Movies";
return (
<div>
<Header title={title} />
<Movies />
</div>
);
}
}
class Header extends React.Component {
render() {
return (
<div>
<h1>{this.props.title}</h1>
</div>
);
}
}
class Movies extends React.Component {
constructor(props) {
super(props);
this.handleMovies = this.handleMovies.bind(this);
this.state = {
movies: []
};
this.handleMovies();
}
handleMovies() {
fetch("https://swapi.co/api/films")
.then(results => {
return results.json();
})
.then(data => {
console.log(data);
let movies = data.results.map(movie => {
return <div key={movie.episode_id}>{movie.title}</div>;
});
this.setState(() => {
return {
movies: movies
};
});
});
}
render() {
return (
<div>
<h1>Episodes</h1>
<div>{this.state.movies}</div>
</div>
);
}
}
ReactDOM.render(<StarWarsApp />, document.getElementById("app"));
To iterate over movies add this in render method:
render(){
return (
<div>
<h1>Episodes</h1>
{
this.state.movies.map((movie, i) => {
return (
<div className="movie" onClick={this.handleClick} key={i}>{movie.title}
<div className="opening">{movie.opening_crawl}</div>
</div>
);
})
}
</div>
);
}
Add this method to your Movies component to add active class on click to DIV with "movie" className:
handleClick = event => {
event.currentTarget.classList.toggle('active');
}
Include this css to your project:
.movie .opening {
display: none;
}
.active .opening {
display: block
}
After fetching the data, just keep it in your state then use the pieces in your components or JSX. Don't return some JSX from your handleMovies method, just do the setState part there. Also, I suggest using a life-cycle method (or hooks API maybe if you use a functional component) to trigger the fetching. By the way, don't use class components unless you need a state or life-cycle methods.
After that, you can render your titles in your render method by mapping the movies state. Also, you can have a place for your opening_crawls part and render it with a conditional operator. This condition changes with a click. To do that you have an extra state property and keep the movie ids there. With the click, you can set the id value to true and show the crawls.
Here is a simple working example.
const StarWarsApp = () => {
const title = "Star Wars";
const subtitle = "Movies";
return (
<div>
<Header title={title} />
<Movies />
</div>
);
}
const Header = ({ title }) => (
<div>
<h1>{title}</h1>
</div>
);
class Movies extends React.Component {
state = {
movies: [],
showCrawl: {}
};
componentDidMount() {
this.handleMovies();
}
handleMovies = () =>
fetch("https://swapi.co/api/films")
.then(results => results.json())
.then(data => this.setState({ movies: data.results }));
handleCrawl = e => {
const { id } = e.target;
this.setState(current => ({
showCrawl: { ...current.showCrawl, [id]: !current.showCrawl[id] }
}));
};
render() {
return (
<div>
<h1>Episodes</h1>
<div>
{this.state.movies.map(movie => (
<div
key={movie.episode_id}
id={movie.episode_id}
onClick={this.handleCrawl}
>
{movie.title}
{this.state.showCrawl[movie.episode_id] && (
<div style={{ border: "1px black solid" }}>
{movie.opening_crawl}
</div>
)}
</div>
))}
</div>
</div>
);
}
}
ReactDOM.render(<StarWarsApp />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
I am using id on the target div to get it back from the event object. I don't like this method too much but for the sake of clarity, I used this. You can refactor it and create another component may be, then you can pass the epoisde_id there and handle the setState part. Or you can use a data attribute instead of id.

How to add a class to only the specifically clicked element in React?

Im looping over an object and displaying li tags and attaching a click handler.
Below that is a game component that displays further information.
Can someone help me with my logic and how to tell React to only add the class to the specific component related to the click.
at
Currently when I click one element, all of the following game components open at the same time. I just want the one below to open.
Library Component
class Library extends React.Component {
state = {
active: false
};
toggleClass = index => {
let active = this.state.active;
active = !active;
this.setState({ active });
};
render() {
return (
<section>
<h2>Game Library</h2>
<ul>
{this.props.games.map((game, index) => (
<Fragment key={`${index}-${game.name}`}>
<li
key={`${index}-${game.gameId}`}
onClick={() => this.toggleClass(index)}
>
{game.name}
</li>
<Game
key={index}
index={index}
game={this.props.games[index]}
active={this.state.active}
/>
</Fragment>
))}
</ul>
</section>
);
}
}
Game Component
class Game extends React.Component {
render() {
return (
<div
key={this.props.index}
className={this.props.active ? "show" : "hide"}
>
<p>{this.props.game.name}</p>
<p>{this.props.game.gameId}</p>
<a>Close</a>
</div>
);
}
}
Instead of having a boolean active that you use for each game, you could use an object with a key for each index that indicates if that particular game is active.
Example
class Library extends React.Component {
state = {
active: {}
};
toggleClass = index => {
this.setState(previousState => {
const active = { ...previousState.active };
active[index] = !active[index];
return { active };
});
};
render() {
return (
<section>
<h2>Game Library</h2>
<ul>
{this.props.games.map((game, index) => (
<Fragment key={`${index}-${game.name}`}>
<li
key={`${index}-${game.gameId}`}
onClick={() => this.toggleClass(index)}
>
{game.name}
</li>
<Game
key={index}
index={index}
game={game}
active={this.state.active[index]}
/>
</Fragment>
))}
</ul>
</section>
);
}
}

React - Passing State between siblings?

Basically new to React, I'm a bit confused on how to properly pass states between components. I found a similar question already React – the right way to pass form element state to sibling/parent elements?
but I wonder if you can give me a specific answer for the code below.
Currently the structure of the app includes:
parent component - App
2 childs: SearchBar and RecipesList
The goal is to make an async search on my Meteor collection and display only the recipes that match the search term.
Right now, I'm just showing all the recipes in my Meteor collection.
I've created a stateful component named SearchBar which holds the input value as this.state.term. The idea is to pass the state to RecipesList but I'm not sure if it's the right thing to do. Alternatively I'd let App deal with the state and passing it to the childs. I believe this is a very common scenario, how do you do it?
App
class App extends Component {
render( ) {
return (
<div>
<Navbar/>
<SearchBar/>
<RecipesList/>
</div>
);
}
}
SearchBar
export default class SearchBar extends Component {
constructor( props ) {
super( props );
this.state = {
term: ''
};
}
onInputChange( term ) {
this.setState({ term });
}
render( ) {
return (
<div className=" container-fluid search-bar">
<input value={this.state.term} onChange={event => this.onInputChange(event.target.value.substr( 0, 50 ))}/>
Value: {this.state.term}
</div>
);
}
}
RecipesList
const PER_CLICK = 5;
class RecipesList extends Component {
componentWillMount( ) {
this.click = 1;
}
handleButtonClick( ) {
Meteor.subscribe('recipes', PER_CLICK * ( this.click + 1 ));
this.click++;
}
renderList( ) {
return this.props.recipes.map(recipe => {
return (
<div key={recipe._id} className="thumbnail">
<img src={recipe.image} alt="recipes snapshot"/>
<div className="caption">
<h2 className="text-center">{recipe.recipeName}</h2>
</div>
</div>
);
});
}
render( ) {
return (
<ul className="list-group">
{this.renderList( )}
<div className="container-fluid">
<button onClick={this.handleButtonClick.bind( this )} className="btn btn-default">More</button>
</div>
</ul>
);
}
}
// Create Container and subscribe to `recipes` collection
export default createContainer( ( ) => {
Meteor.subscribe( 'recipes', PER_CLICK );
return {recipes: Recipes.find({ }).fetch( )};
}, RecipesList );
App
class App extends Component {
constructor(props, ctx){
super(props, ctx)
this.state = {
searchQuery: ''
}
this.searchInputChange = this.searchInputChange.bind(this)
}
searchInputChange(event) {
this.setState({
searchQuery: event.target.value.substr( 0, 50 )
})
}
render( ) {
const { searchQuery } = this.state
return (
<div>
<Navbar/>
<SearchBar onChange={this.searchInputChange} value={searchQuery}/>
<RecipesList searchQuery={searchQuery}/>
</div>
)
}
}
The App component takes care of the state and this is then passed down to the children as props the seach term is available to RecipesList through props.searchQuery.
The searchInputChange handler is passed down to the SearchBar as props.
SearchBar
export default const SearchBar = ({value, onChange}) => (
<div className=" container-fluid search-bar">
<input value={value} onChange={onChange}/>
Value: {value}
</div>
)
Since the SearchBar delegated state to the parent component, we can use a stateless react component as we only need information from the props to render it.
In general it is always best to have a logical or stateful or controller component take care of state and the logic, this component then passes down state and methods to presentational or view components which take care of what the user sees and interacts with.
Define the state term up in to the App component.
Also write the handleInput function and pass it to the SearchBar component as porps
handleInput(val) {
this.setState({
term: val,
});
}
When something in the search bar is typed(onKeyUp) add the listener handleInput.
Also create <RecipesList searchQuery={this.state.term}/>
now in the render function RecipesList filter out the recipes you want to display from your list

Categories