react-list has a method "getVisibleRange()". getVisibleRange() => [firstIndex, lastIndex]. The examples show accessing this like so:
onScrollHandler() {
console.log('onScrollHandler visible', this.getVisibleRange());
}
with the "this" keyword, within a class component. Is it possible to access the getVisibleRange() method within a functional component? For example:
const handleScroll = () => {
let [firstIndex, lastIndex] = getVisibleRange() <-- ??
}
<div id="list" onScroll={handleScroll}></div>
UPDATE: reproducable code
import React, {useState, useEffect} from 'react'
import ReactList from 'react-list'
var faker = require('faker')
const TalentSearch = () => {
let items = [...new Array(500)].map(() => faker.fake(faker.name.findName()))
const renderItem = (index, key) => {
return <div key={key}>{items[index]}</div>
}
const handleScroll = () => {
// access getVisibleRange() here?
}
return (
<div>
<h1>Search Results</h1>
<div id="list" style={{overflow: 'auto', maxHeight: 400}} onScroll={handleScroll}>
<ReactList
itemRenderer={renderItem}
length={items.length}
initialIndex={50}
type='uniform'
scrollTo={50}
/>
</div>
</div>
)
}
export default TalentSearch
You need to access it through a reference, with hooks you may use useRef:
const TalentSearch = () => {
const listRef = useRef();
return <ReactList ref={listRef} />;
};
Then you can access the methods like so:
listRef.current.getVisibleRange();
Related
I'm creating a counter state using useState and useRef. However I'm getting this error
Here's my code
import { useEffect, useRef, useState} from 'react';
const App = () => {
const [clicks, setClick] = useState(0)
const myComponentDiv = useRef(null)
useEffect(() => {
if (myComponentDiv && myComponentDiv.current) {
myComponentDiv.current.addEventListener('click', clickHandler)
return () => {
myComponentDiv.current.removeEventListener('click', clickHandler)
}
}
}, [myComponentDiv]);
const clickHandler = () => {
setClick(clicks + 1)
}
return (
<div className="App">
<div className="my-component" ref="myComponentDiv">
<h2>My Component {clicks} clicks</h2>
</div>
</div>
);
}
export default App;
May i know where i did wrong?
Here:
ref="myComponentDiv"
should be:
ref={myComponentDiv}
import React, {useState, useEffect} from 'react';
import {connect} from 'react-redux';
import {
fetchRecipes
} from '../../store/actions';
import './BeerRecipes.css';
const BeerRecipes = ({recipesData, fetchRecipes}) => {
const [page, setPage] = useState(1);
const [recipes, setRecipes] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchRecipes();
}, [])
return (
<div className='beer_recipes_block'>
<div className='title_wrapper'>
<h2 className='title'>Beer recipes</h2>
</div>
<div className='beer_recipes'>
<ul className='beer_recipes_items'>
{
recipesData && recipesData.recipes && recipesData.recipes.map(recipe =>
<li className='beer_recipes_item' id={recipe.id}>{recipe.name}</li>
)
}
</ul>
</div>
</div>
);
};
const mapStateToProps = state => {
return {
recipesData: state.recipes
}
}
const mapDispatchToProps = dispatch => {
return {
fetchRecipes: () => dispatch(fetchRecipes())
}
}
export default connect(mapStateToProps, mapDispatchToProps)(BeerRecipes);
this is my component where I would like to create infinite scroll and below is my redux-action with axios:
import axios from "axios";
import * as actionTypes from "./actionTypes";
export const fetchRecipesRequest = () => {
return {
type: actionTypes.FETCH_RECIPES_REQUEST
}
}
export const fetchRecipesSuccess = recipes => {
return {
type: actionTypes.FETCH_RECIPES_SUCCESS,
payload: recipes
}
}
export const fetchRecipesFailure = error => {
return {
type: actionTypes.FETCH_RECIPES_FAILURE,
payload: error
}
}
export const fetchRecipes = (page) => {
return (dispatch) => {
dispatch(fetchRecipesRequest)
axios
.get('https://api.punkapi.com/v2/beers?page=1')
.then(response => {
const recipes = response.data;
dispatch(fetchRecipesSuccess(recipes));
})
.catch(error => {
const errorMsg = error.message;
dispatch(fetchRecipesFailure(errorMsg));
})
}
}
I want to create a scroll. I need, firstly, to display first 10 elements and then to add 5 elements with every loading. I have 25 elements altogether and when the list is done it should start from the first five again.
Assuming you already have everything ready to load your next page. You can probably simplify the entire process by using a package like react-in-viewport so you don't have to deal with all the scroll listeners.
then you use it like this way.
import handleViewport from 'react-in-viewport';
const Block = (props: { inViewport: boolean }) => {
const { inViewport, forwardedRef } = props;
const color = inViewport ? '#217ac0' : '#ff9800';
const text = inViewport ? 'In viewport' : 'Not in viewport';
return (
<div className="viewport-block" ref={forwardedRef}>
<h3>{ text }</h3>
<div style={{ width: '400px', height: '300px', background: color }} />
</div>
);
};
const ViewportBlock = handleViewport(Block, /** options: {}, config: {} **/);
const Component = (props) => (
<div>
<div style={{ height: '100vh' }}>
<h2>Scroll down to make component in viewport</h2>
</div>
<ViewportBlock
onEnterViewport={() => console.log('This is the bottom of the content, lets dispatch to load more post ')}
onLeaveViewport={() => console.log('We can choose not to use this.')} />
</div>
))
What happen here is, it creates a 'div' which is outside the viewport, once it comes into the view port ( it means user already scrolled to the bottom ), you can call a function to load more post.
To Note: Remember to add some kind of throttle to your fetch function.
I've seen examples of the useObserver hook that look like this:
const Test = () => {
const store = useContext(storeContext);
return useObserver(() => (
<div>
<div>{store.num}</div>
</div>
))
}
But the following works too, and I'd like to know if there's any reason not to use useObserver to return a value that will be used in render rather than to return the render.
const Test = () => {
const store = useContext(storeContext);
var num = useObserver(function (){
return store.num;
});
return (
<div>
<div>{num}</div>
</div>
)
}
Also, I don't get any errors using useObserver twice in the same component. Any problems with something like this?
const Test = () => {
const store = useContext(storeContext);
var num = useObserver(function (){
return store.num;
});
return useObserver(() => (
<div>
<div>{num}</div>
<div>{store.num2}</div>
</div>
))
}
You can use observer method in the component. And use any store you want.
import { observer } from "mobx-react-lite";
import { useStore } from "../../stores/StoreContext";
const Test = observer(() => {
const { myStore } = useStore();
return() => (
<div>
<div>{myStore.num}</div>
<div>{myStore.num2}</div>
</div>
)
}
);
StoreContext.ts
import myStore from './myStore'
export class RootStore{
//Define your stores here. also import them in the imports
myStore = newMyStore(this)
}
export const rootStore = new RootStore();
const StoreContext = React.createContext(rootStore);
export const useStore = () => React.useContext(StoreContext);
I'm trying to make a page where there are multiple instances of the same functional component inside a parent component. Upon clicking anywhere inside the parent component I want each individual child component to check if the 'event.target' is equal to itself, if it isn't, then I want it to do something.
Maybe I got the wrong idea about this, but is there a way to use a 'this' property inside a function component or a way to mimic it?
edit: some code
Parent component:
import React, { useState } from "react"
import ShopppingListItem from './ShoppingListItem'
function ShopppingList(){
const [list, setList] = useState([{id: 0,text: 'dfdsdf'}, {id: 1,text: 'dfdsdf'},
{id: 2,text: 'dfdsdf'}, {id: 3,text: 'dfdsdf'}]) //shopping list example
const [clickedTarget, setClickedTarget] = useState(null)
const handleClick = (e) => {
setClickedTarget(e.target)
}
return(
<ol
className='shopping-list-container'
onClick={handleClick}
>
{
list.map(item => {
return <ShopppingListItem key={item.id} item={item} clickedTarget={clickedTarget}/>
})
}
</ol>
)
}
export default ShopppingList
Child component:
import React, { useState } from 'react'
function ShoppingListItem(props){
const id = props.item.id
const [text, setText] = useState(props.item.text)
const [editMode, setEditMode] = useState(false)
const clickedTarget = props.clickedTarget
const inputStyle={
width: "15vw",
border:"0px",
font:"400 20px Segoe UI",
paddingLeft:"1px"
}
//////////////////////////////////////
React.useEffect(() => {
if (this != clickedTarget) //the idea behind what im trying to achieve
{ do something }
}, [clickedTarget])
//////////////////////////////////////
const handleTextClick = () => {
setEditMode(!editMode)
}
const handleChange = (e) => {
setText(e.target.value)
}
return(
<div className='shopping-list-item'>
<span style={{paddingRight:"5px"}}>{id}. </span>
{!editMode
? <span onClick={handleTextClick}>{text}</span>
: <input
ref={element}
style={inputStyle}
type="text"
value={text}
placeholder={text}
onChange={handleChange}
/>}
</div>
)
}
export default ShoppingListItem
Why am I not having access to the updated recipes (useState) value from inside the component that defines it?
In this example you can see how not being able to access to this value causes an error in the app once the reference to a function that I use to update the state is deleted
=> Codebox and code below
*Click two times the <h1> to see the error
https://codesandbox.io/s/sparkling-sea-5iqgo?fontsize=14&hidenavigation=1&theme=dark
import React, { useEffect, useState } from "react";
export default function App() {
const [userRecipes, setUserRecipes] = useRecipesData();
return (
<div className="App">
<h1
onClick={() => {
userRecipes.setBookmarks("onetwothree");
}}
>
Hello CodeSandbox
</h1>
<h2>{userRecipes.bookmarked_recipes}</h2>
</div>
);
}
const useRecipesData = () => {
const [recipes, setRecipes] = useState({});
const setBookmarks = newRecipes => {
console.log(recipes); // is undefined !? and deletes setBookmarks
setRecipes({
bookmarked_recipes: newRecipes,
setBookmarks: recipes.setBookmarks
});
};
useEffect(() => {
setRecipes({
bookmarked_recipes: "testtesttest",
setBookmarks: setBookmarks
});
}, []);
return [recipes, setRecipes];
};
What I don't understand is why if I return [recipes, setRecipes] where recipes.setBookmarks stores a reference to a function, it doesn't work
But if I return the function itself (which is a reference as well) [recipes, setBookmarks] then it works
See this other codebox where it does work
https://codesandbox.io/s/red-violet-gju99?fontsize=14&hidenavigation=1&theme=dark
import React, { useEffect, useState } from "react";
import "./styles.css";
export default function App() {
const [userRecipes, setUserRecipes] = useRecipesData();
return (
<div className="App">
<h1
onClick={() => {
setUserRecipes("onetwothree" + Math.random());
}}
>
Hello CodeSandbox
</h1>
<h2>{userRecipes.bookmarked_recipes}</h2>
</div>
);
}
const useRecipesData = () => {
const [recipes, setRecipes] = useState({});
const setBookmarks = newRecipes => {
console.log(recipes); // is defined this time
setRecipes({
bookmarked_recipes: newRecipes,
setBookmarks: recipes.setBookmarks
});
};
useEffect(() => {
setRecipes({
bookmarked_recipes: "testtesttest",
setBookmarks: setBookmarks
});
}, []);
return [recipes, setBookmarks];
};
It's all about context.
If you'll put console.log(receipes) in useEffect and the render function itself, you can see what the flow of events are:
First render recipe is empty.
UseEffect is called and puts setBookmark in recipe (but the recipe for setBookmark is empty)
Second render is called, and now recipe has "testesttest" and recipe.setBookmark is a function where the recipe object that is bound to it is the recipe value from event 1
setBookmark is called, recipe is now set to "onetwothree" but the recipe object is empty so we set the setBookmark to undefined.
instead of keeping the function inside the state, you need to just call it directly (I.E. return setBookmark and not setRecipes, like this:
import React, { useEffect, useState } from "react";
import "./styles.css";
export default function App() {
const [userRecipes, setBookmarks] = useRecipesData();
return (
<div className="App">
<h1
onClick={() => {
setBookmarks("onetwothree" + Math.random());
}}
>
Hello CodeSandbox
</h1>
<h2>{userRecipes.bookmarked_recipes}</h2>
</div>
);
}
const useRecipesData = () => {
const [recipes, setRecipes] = useState({});
const setBookmarks = newRecipes => {
setRecipes({
bookmarked_recipes: newRecipes,
});
};
useEffect(() => {
setRecipes({
bookmarked_recipes: "testtesttest",
});
}, []);
return [recipes, setBookmarks];
};