Libraries like react-virtualized, react-window and react-virtuoso have item count property like in code below from materal-ui. However it is located within return. Is there any way to make item counterupdatable?
export default function VirtualizedList() {
const classes = useStyles();
return (
<div className={classes.root}>
<FixedSizeList height={400} width={300} itemSize={46} itemCount={200}>
{renderRow}
</FixedSizeList>
</div>
);
}
Yes you can pass on a dynamic value to the itemCount property in FixedSizeList. It take care of it and also ensure that the scroll remain where it is currently
A sample code would look like
const Example = () => {
const [rowCount, setRowCount] = useState(10);
useEffect(() => {
setTimeout(() => {
console.log("changed");
setRowCount(1000);
}, 10000);
}, []);
console.log(rowCount);
return (
<List
className="List"
height={150}
itemCount={rowCount}
itemSize={35}
width={300}
>
{Row}
</List>
);
};
Working demo
Related
I'm somewhat new to javascript and react. Working on a personal project, and I'm trying to build functionality where a user can add and remove custom player configurations. At the moment, it seems the best way to do this is to have buttons to add and remove components as need.
This is what I have at the moment:
import React, { Component, useState } from 'react';
import Button from '#material-ui/core/Button'
import ButtonGroup from '#material-ui/core/ButtonGroup';
import Container from '#material-ui/core/Container'
import Grid from '#material-ui/core/Grid'
import DeleteIcon from '#material-ui/icons/Delete'
function Stats({ allStats, editedStats, statPlayerKey, statKey }) {
const removeStats = () => {
const filteredStats = allStats.filter((stat) => stat.statKey !== statKey);
editedStats(filteredStats);
}
return (
<Grid containter>
<Button onClick={removeStats}>
<DeleteIcon />
</Button>
<Grid item>
{statPlayerKey} {statKey}
</Grid>
</Grid>
)
}
function Player({ allPlayers, editedPlayers, playerKey, name }) {
const [stats, setStats] = useState([])
const handleRemove = () => {
const filteredPlayers = allPlayers.filter((player) => player.playerKey !== playerKey);
editedPlayers(filteredPlayers);
};
const statsList = stats.map(({statKey, statPlayerKey}) => (
<Container>
<Stats
allStats={stats}
statKey={statKey}
statPlayerKey={statPlayerKey}
editedStats={setStats}
/>
</Container>
))
const addStat = () => {
setStats([...stats, {statPlayerKey: playerKey, statKey: uuid()}])
}
return (
<Grid container>
<Button onClick={handleRemove}>
<DeleteIcon />
</Button>
<Grid item>
{playerKey} {name}
</Grid>
<Grid item>
{statsList}
</Grid>
<Button onClick={() => addStat()}>
Add Stat
</Button>
</Grid>
);
};
function TestApp() {
const [players, setPlayers] = useState([
{
playerKey: uuid(),
name: "Mario"
},
{
playerKey: uuid(),
name: "Luigi"
},
{
playerKey: uuid(),
name: "Toad"
}
]);
const addPlayer = () => {
setPlayers([...players, {
playerKey: uuid(),
name: 'Bowser'
}])
}
const playersList = players.map(({ playerKey, name }) => (
<Container>
<Player
allPlayers={players}
editedPlayers={setPlayers}
playerKey={playerKey}
name={name}
/>
</Container>
));
return (
<div className="App">
<h1>Team Members ({players.length})</h1>
<Container>{playersList}</Container>
<Button onClick={addPlayer}>ADD</Button>
</div>
);
}
export default TestApp;
Side notes - I'm using uuid's at the moment to track what's happening. Not sure if I should be trying to call those in the remove functions or not. Sorry for the current lack of front end design, planning to use material-ui, but I'm waiting to design the rest until I can get the functionality working as intended.
What am I doing wrong? Components are added easily enough, but the remove functionality is buggy. If you add a stat component to a player component and delete that player component, it passes the stat component to the next player in the list. I'd like the delete player button to delete the stat component(s) associated with the as well. I'd also like to add components within the stat components, so I want to make sure I'm executing this "components within components" method as efficiently as possible.
You have to remove State Value not Component, For that you have to make one function regarding removeState after that you able to remove function and function contains array' value remove from your current array.
Please do like remove array value(object) from state using splice
array.splice(index, 1);
in your case do like this :
removeState(e) {
var array = [...this.state.people]; // make a separate copy of the array
var index = array.indexOf(e.target.value)
if (index !== -1) {
array.splice(index, 1);
this.setState({people: array});
}
},
I am using react-transition-group to fade out various components. I'm converting simple conditional renders such as:
{valueToDisplay && <MyComponent {...valueToDisplay} />}
To transitions such as:
<CSSTransition
in={!!valueToDisplay}
unmountOnExit
classNames="fade"
addEndListener={(node, done) => node.addEventListener("transitionend", done, false)}
>
<MyComponent {...valueToDisplay} />
</CSSTransition>
The issue I'm running into is when the "in" property of the transition becomes false, and the exit transition is running, the child component will now have null prop values. This can cause exceptions or cause the child content to flash and change during the exit. What I would like to see instead is that during the exit transition, the content will remain unchanged.
The first solution I came up with was to make child components to cache previous values of their props, and then use those previous values when their props become null. However I don't like this solution because it forces all components which will be transitioned to introduce new and confusing internal logic.
The second attempt I made was to create a wrapper component which cached the previous value of props.children, and whenever "in" becomes false, renders the cached children instead. This essentially "freezes" the children as they were the last time in was true, and they don't change during the exit transition. (If this solution is the general practice, is there a better way of doing this, perhaps with the useMemo hook?)
For such a common use case of fading content out, this solution doesn't seem very intuitive. I can't help but feeling I'm going about this the wrong way. I can't really find any examples of having to cache/memoize content to keep it displaying during fade outs. It seems like something somewhere has to remember the values to display when performing the exit transition. What am I missing?
Here is a minimal example and working example:
import { useRef, useEffect, useState } from 'react';
import { createRoot } from 'react-dom/client';
import { CSSTransition } from 'react-transition-group';
const Pet = ({ type, age }) => {
return (
<div>
Your pet {type || 'null'} is age {age || 'null'}
</div>
);
};
const Fade = ({ show, children }) => {
const nodeRef = useRef(null);
return (
<CSSTransition
nodeRef={nodeRef}
in={show}
unmountOnExit
classNames="fade"
addEndListener={(done) => nodeRef.current.addEventListener("transitionend", done, false)}
>
<span ref={nodeRef}>
{children}
</span>
</CSSTransition>
);
};
const FadeWithMemo = ({ show, children }) => {
const previousChildren = useRef();
useEffect(() => {
previousChildren.current = show ? children : null;
}, [show, children]);
return (
<Fade show={show}>
{show ? children : previousChildren.current}
</Fade>
);
};
const Example = () => {
const [currentPet, setCurrentPet] = useState(null);
const getPet = () => {
return {
type: (Math.random() > .5) ? 'Cat' : 'Dog',
age: Math.floor(Math.random() * 15) + 1
};
};
return (
<>
<button onClick={() => setCurrentPet(getPet())}>Set</button>
<button onClick={() => setCurrentPet(null)}>Clear</button>
<div>
The Problem:
<Fade show={!!currentPet}>
<Pet {...currentPet} />
</Fade>
</div>
<div>
Potential Fix:
<FadeWithMemo show={!!currentPet}>
<Pet {...currentPet} />
</FadeWithMemo>
</div>
</>
);
};
const root = createRoot(document.getElementById('root'));
root.render(<Example />);
You can detach the visible condition from the pet state so that you have more granular control over whether something is visible and what is actually being displayed.
const Example = () => {
const [currentPet, setCurrentPet] = useState(null);
const [showPet, setShowPet] = useState(false);
const getPet = () => {
return {
type: (Math.random() > .5) ? 'Cat' : 'Dog',
age: Math.floor(Math.random() * 15) + 1
};
};
return (
<>
<button onClick={() => {
setCurrentPet(getPet());
setShowPet(true);
}}>Set</button>
<button onClick={() => setShowPet(false)}>Clear</button>
<div>
<Fade show={showPet}>
<Pet {...currentPet} />
</Fade>
</div>
</>
);
};
or you can have the visible be part of the pet state and only set that part to false.
recently I have been using react-virtualized library to render my tree item view. I have followed example from the docs however I end up having very strange problem with items disappearing when I scroll down.
I have created codesandbox to show this behaviour and code.
https://codesandbox.io/s/bitter-snow-23vci?file=/src/App.js
Main idea of virtualized list to render it as a list.
If you pass down tree like structure and render it like in your code sample
<List
....
rowCount={data.length}
/>
You don't change rowCount value and keep expanded state in your Node component.
const Node = ({ data, listRef, depth }) => {
const [isExpanded, setIsExpanded] = React.useState(false);
But then you scroll out of screen your Node element will be destroyed and recreated then you return.
You need to keep your selections outside of Node element.
like
// [key]: value structure there key is id of element and value [true, false].
const rootObject = {[elementId]: true};
const App = () => {
const [visibleNodes, setVisibleNodes] = useState(rootObject)
....
<List
...
rowRenderer={({ index, style, key }) => {
return (
<Node
setVisibleNodes={setVisibleNodes}
visibleNodes={visibleNodes}
style={style}
key={key}
data={data[index]}
listRef={ref}
depth={1}
/>
);
}}
rowCount={data.length}
width={width}
/>
And in Node
const Node = ({ data, listRef, depth, setVisibleNodes, visibleNodes }) => {
const isExpanded = visibleNodes[data.id];
const handleClick = (e) => {
if (data.children.length === 0) return;
e.stopPropagation();
setVisibleNodes({...visibleNodes, [data.id]: !!isExpanded});
listRef.current.recomputeRowHeights();
listRef.current.forceUpdate();
};
return (
<div onClick={handleClick}>
{data.children.length ? (isExpanded ? "[-]" : "[+]") : ""} {data.name}
{isExpanded && (
<div style={{ marginLeft: depth * 15 }}>
{data.children.map((child, index) => (
<Node
key={index}
data={child}
listRef={listRef}
depth={depth + 1}
/>
))}
</div>
)}
</div>
);
};
I think it works)
But it's better to do such things like real list and make tree hierarchy just visually. By that way you'll use Virtualisation List as it was purposed by creators)
So I have this Display() function which takes events from the Google Calendar and the function returns a list of elements (each element is associated with a slider) to be rendered on the screen (see return statement of Display() function) and renders them as seen here. So each element comes with a Remove button so that I can remove an unwanted element from the page using the hideMe() function inside the Display() function. The hideMe() function does seem to do its work, however, it removes all the elements on the page as seen here. So I am struggling to figure out what I should fix so that when I click on the Remove button, it only removes the element and the slider associated to that remove button. I am new to React and JavaScript so please help. Any help is appreciated and thank you in advance.
function Display() {
const isMounted = useRef(true);
const [items, saveItems] = useState([]);
const [visible, setVisible] = useState(true);
const [fading, setFading] = useState(false);
useEffect(() => {
return () => {
isMounted.current = false;
};
}, []);
useEffect(() => {
(async () => {
const items = await fetchItems();
//Do not update state if component is unmounted
if (isMounted.current) {
saveItems(items);
}
})();
}, []);
function hideMe() {
setFading(true);
setTimeout(() => setVisible(false), 650);
}
return (
<Tab.Pane attached={false}>
<h5>Rate stress level for each event</h5>
<br/>
{items.map(item => (
<div key={item.id} isvisible={!fading}
style={visible ? null : { display: "none" }}>
<Typography id="discrete-slider-restrict" gutterBottom>
{item}
<button onClick={hideMe}>Remove</button>
</Typography>
<PrettoSlider aria-label="pretto slider" defaultValue={98} step={null}marks={stresslevel}/>
</div>
))}
</Tab.Pane>
)
}
It seems to me that this issue is happening because all elements are available in same state or i would say that they all share same state. So, this executes for all. If it is possible for you to extract it to a new component and use the hideMe function there. This will i am sure work for each individual elements.
It is my suggestion please go through below. May be you have to tweak a little bit.
You can extract the elements in a separate component like:
const Item = props => {
const [visible, setVisible] = useState(true);
const [fading, setFading] = useState(false);
function hideMe() {
setFading(true);
setTimeout(() => setVisible(false), 650);
}
return (
<div isvisible={!fading} style={visible ? null : { display: "none" }}>
<Typography id="discrete-slider-restrict" gutterBottom>
{item}
<button onClick={hideMe}>Remove</button>
</Typography>
<PrettoSlider aria-label="pretto slider" defaultValue={98}
step={null} marks={stresslevel}/>
</div>
);
};
export default Item;
Then you can use it like:
// import Item
{items.map(item => (
<Item key={item.id} itemObj={item} />
// in case if you need item obj then props.itemObj will get you the object.
))}
In this way you can manage the hideMe function with the separate specific Item component.
I have a component that is checking if some state is true or false. I show a <p> tag if true and hide a <h3>. I am pulling the data from a gaphQL query so there is a map method and there are three <Card>'s now if I click the card and run my showFull function it shows the p tags on all the elements, instead I just want to isolate the specific one it is clicking on.
Here is my component
<Testimonials className="testimonaials">
{data.allDatoCmsTestimonial.edges.map(({ node: testimonial }) => (
<Card onClick={showFull} background={testimonial.testimonialImage.url}>
{testimonialFull ?
<p>{testimonial.fullLengthQuote}</p>
:
<h3>{testimonial.shortQuote}</h3>
}
</Card>
))}
</Testimonials>
Here is my state and my function
const [testimonialFull, setTestimonialFull] = useState(false)
const showFull = () => {
setTestimonialFull(true)
}
Attempting Alexander's answer. The issue I am having now is Cannot read property 'testimonialImage' of undefined
Here is the component
const IndexPage = ({ data }) => {
const TestimonialCard = ({testimonial})=>{
const [showFull, setShowFull] = useState(false)
const handleClick = useCallback(()=>{
setShowFull(true)
//setShowFull(s=>!s)//If you want toggle behaviour
},[])
return <Card onClick={handleClick} background={testimonial.testimonialImage.url}>
{showFull ?
<p>{testimonial.fullLengthQuote}</p>
:
<h3>{testimonial.shortQuote}</h3>
}
</Card>
}
return (
...
Here is where I invoke it in the map function
...
return (
... (bunch of other jsx/html)
<Testimonials className="testimonaials">
{data.allDatoCmsTestimonial.edges.map(({ node: testimonial }) => (
<TestimonialCard/>
))}
</Testimonials>
...
Wrap the cards in a custom component
const TestimonialCard = ({testimonial})=>{
const [showFull, setShowFull] = useState(false)
const handleClick = useCallback(()=>{
setShowFull(true)
//setShowFull(s=>!s)//If you want toggle behaviour
},[])
return <Card onClick={handleClick} background={testimonial.testimonialImage.url}>
{showFull ?
<p>{testimonial.fullLengthQuote}</p>
:
<h3>{testimonial.shortQuote}</h3>
}
</Card>
}