There is a block of content:
<div className="col-7">
{content}
</div>
A component is placed in "content" depending on the state:
switch (activeItem) {
case "home": {
content = <AppHome/>
break;
}
case "about-me": {
content = <AppAboutMe/>
break;
}
default:
content = null
}
How to add content change animation so that one disappears smoothly, the other appears?
I tried to add animation through CSS class. It worked, but the disappearance was interrupted by the appearance. I tried through CSS transition but it appeared only once and did not disappear. When the content was subsequently changed, the animations no longer worked.
Here's a little CodeSandbox project I made to refer as an example:
To handle animation among several components, I recommend having all of your available components in a single constant. For example:
const components = {
title: {
key: '1',
content: <div>New York</div>,
},
body: {
key: '2',
content: (
<div>
<ul>
<li>cheese</li>
<li>tomato</li>
<li>onions</li>
</ul>
</div>
),
},
};
And then you can initialise your state with the wanted ones.
const [fields, setFields] = useState([]); // [] || [components.title]
Then, depending on your logic you can either add components on top of existing ones or remove by setting your state.
const onEnter = () => {
setFields([...fields, components.title]);
};
const onExit = () => {
setFields([]);
};
Lastly, in order to animate state changes, you can wrap your components with <motion.div>{component}<motion.div/>, setting variants will take care of the details of your animation process.
Related
I'm building my first React demo. I'd like to learn how to write a function and/or useEffect that will display an article based on which button was clicked. I have 4 buttons always visible and I'd like to have 1 article visible depending on which one is selected. Thanks in advance for the assistance!
Here's the setup I have so far:
import React from 'react'
import './main.css'
import mainData from './mainData'
import Button from './menuButtons'
import Article from './contentCard'
function Main() {
const [menuProps] = React.useState(mainData.menu)
const [buttons, setButtonActive] = React.useState(mainData.buttons)
const [articles, setArticle] = React.useState(mainData.articles[0])
function setActive(key) {
setButtonActive(prevActive => {
return prevActive.map((button) => {
return button.key === key ? {...button, active: true} : {...button, active: false}
})
})
}
const buttonElements = buttons.map((buttonProps) => (
<Button
key={buttonProps.key}
label={buttonProps.label}
active={buttonProps.active}
setactive={() => setActive(buttonProps.key)}
/>
))
return (
<main className='content-container flex'>
<section className='menu flex'>
<img src={menuProps.img} alt='Travelogue splash images'/>
<h1>{menuProps.title}</h1>
<p>{menuProps.description}</p>
<div className='menu-buttons flex'>
{buttonElements}
</div>
</section>
<Article
key={articles.key}
style={{backgroundImage: `url(${articles.img})`}}
title={articles.title}
description={articles.description}
/>
</main>
)
}
export default Main
I wanted to share the function structure that worked for me in this scenario...
[For a little more clarification, I'm using a separate data JS file that contains the content, mainData as shown above, that gets mapped in the main.js file for both the buttons and articles. Button keys are set in the data file with a value the same as an index]
function setButtonActive(key) {
setActive(prevActive => {
return prevActive.map((button, index) => {
if (key === index) {
setArticle(mainData.articles[index])
}
return button.key === key ?
{...button, active: true} : {...button, active: false}
})
})
}
The site is now live so everyone can see the finished result of what I was aiming for:
https://willfranck.github.io/react-demo-travel-site/
Thanks!
With your state, how are you determining which button is active?
const [buttons, setButtonActive] = React.useState(mainData.buttons)
const [articles, setArticle] = React.useState(mainData.articles[0])
Here it looks you are mixing two concepts - setting elements as buttons and setting a button as active. I'm not sure what your mainData.buttons is, but unless you are planning on changing the buttons based on user actions, you don't need to set them in state. You've imported them, you can use them.
You should use useState for setting a button as active - how you decide to do that is ultimately up to you. The same is true of articles. How are buttons and articles related? From there, you can determine how to structure your state.
I'm making an infinite scroll up of the chat room, judging to see the last message and loading the data, but the scrollbar remains at the top of the page, you know, it will automatically execute the call api infinitely,how to keep the scrollbar at the previous message in the position
If you have a reference of the new element that is added, you can use element.scrollIntoView() to make sure it's visible after rendering.
https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView
But:
In chat-rooms, this may not always be the right thing to do, as the user may have scrolled up to copy/paste something, and losing the position would be annoying. So check if the chat was already on the bottom before you scroll via JS.
You can take a look at how twitch-chat works to get a good example. The user gets an info if there are new chat messages.
This solution is for library https://github.com/ankeetmaini/react-infinite-scroll-component
The structure I used is based on props passed to a child component. Parent component is doing the fetching and controlling the state.
Parent Component:
const fetchScrollData = () => {
// Here we set that we are not able to do scrolling fetching
setIsLoadingScrollData(true);
// Apply here some pagination calculation
searchData()
.then((tempData) => {
// Here i am saving the id of the first message
// I had before scrolling
setShouldScrollToId(data[0].id);
const newData = tempData.concat(data);
setData(newData);
// Apply here some logic, depending if you have finished or not
setHasMoreData(true);
})
.then(() => {
setTimeout(() => {
// Change the timeout accordingly
// hack for setting the isloading to false when everything is done
setIsLoadingScrollData(false);
}, 1500);
});
};
Child Component
type PropsType = {
fetchScrollData: () => void;
hasMoreData: boolean;
data: string[];
isLoadingScrollData: boolean;
shouldScrollToId: string;
};
const ChildComponent: FC<PropsType> = (props: PropsType) => {
useEffect(() => {
if (props.shouldScrollToId !== '') {
const element = document.getElementById(props.shouldScrollToId);
if (element) {
element.scrollIntoView({ behavior: 'smooth' });
}
}
});
....
return (
<div
className="message-list overflow-scroll"
id="scrollableDiv"
style={{
display: 'flex',
flexDirection: 'column-reverse'
}}>
<InfiniteScroll
dataLength={props.data.length}
hasMore={props.hasMoreData && !props.isLoadingScrollData}
inverse={true}
next={props.fetchScrollData}
scrollableTarget="scrollableDiv"
style={{ display: 'flex', flexDirection: 'column-reverse', perspective: '1px' }}
loader={<></>}>
<div>
{props.data.map((d, index) => {
// Here i am setting the id which the useEffect will use to do the scroll
<div key={index} id={d.id} >div - #{message}</div>;
})}
</div>
{props.isLoadingScrollData ? <Spinner></Spinner> : null}
</InfiniteScroll>
</div>
I had some problems with the loader, so I decided not use it. Instead I created my own Spinner Component and make it appear or disappear according to the prop which shows if we are still fetching data from the scroll.
I have a container populated dynamically with elements based on an array in the Redux store. A code example follows.
const state = {
elements: [
{
id: "element_1",
attrs: {
width: 200,
height: 100,
backgroundColor: "#ff0000"
}
},
{
id: "element_2",
attrs: {
width: 50,
height: 300,
backgroundColor: "#00ffff"
}
}
]
};
// Elements Container (elements coming from Redux)
const ElementsContainer = ({ elements, elementsRefs }) => (
<>
{elements.map(element => (
<div
key={element.id}
ref={el => {
elementsRefs.current[element.id] = el;
}}
/>
))}
</>
);
// Parent container (elements coming from Redux)
const ParentContainer = ({ elements }) => {
const elementsRefs = React.useRef({});
React.useEffect(() => {
// ...some code to animate the elements through the refs
}, [elements]);
return (
<>
<ElementsContainer elementsRefs={elementsRefs} />
</>
);
};
The issue is that Redux dispatches the updated state first to the parent, then to the child (ElementsContainer). Whenever I add an element to the state, the refs are assigned only after useEffect (which is meant to trigger some DOM animations with the GSAP library leveraging the ref) has been executed, meaning that useEffect cannot find the ref yet.
So far this is how I solved:
I added an early return inside useEffect in case the ref doesn't exist
I created a state in the parent that keeps count of the elements manipulation
I pass down the setter of the state to the element, triggering it inside the ref callback
I added the state that keeps count as a dependency of useEffect
All of this causes two renders for each manipulation of the elements list. I really dislike this solution and I wonder whether you can suggest a better pattern.
I made a PoC of the app using Context instead of Redux and it works as intended (here you can find the fiddle), but I still would like to hear suggestions.
In Svelte, I have a component which is used to display items in two different lists. When those items are moved from one list to the other, they use a transition to animate in or out.
However, I also have a way to filter what is displayed on the screen. Displaying a new set of items would use the same component, but with different data. In this case, I don't want the transition animation to occur. I assumed that adding the local modifier would do the trick, but it seems that Svelte isn't dropping the parent element to the list, but instead reusing it and adding the new data in the existing list DOM element.
I've tried to reproduce what I'm seeing in the sample code below.
Wanted Behavior:
Clicking on a TODO will toggle the TODO from one list to the other.
Clicking "Switch Categories" will switch which TODOs are listed, without animating the <li>s of the TODOs that are added or removed.
Actual Behavior:
Happens as expected.
The todos that are switched in do so with the animation.
How can I change my example so that I get the effect that I want?
App.svelte:
<script>
import Todos from './Todos.svelte';
let todos = [
{ id: 1, category: 'personal', name: 'Walk dog', done: false },
{ id: 2, category: 'personal', name: 'Take out trash', done: false },
{ id: 3, category: 'work', name: 'Make login page functional', done: false },
{ id: 4, category: 'work', name: 'Make login page elegant', done: false }
];
let currentCategory = 'personal';
const toggleCategory = () => {
currentCategory = currentCategory == 'personal' ? 'work' : 'personal';
}
const toggleTodo = id => {
todos = todos.map(todo => {
if (todo.id === id) {
return { ...todo, done: !todo.done }
}
return todo;
});
}
$: categoryTodos = todos.filter(x => x.category == currentCategory);
</script>
<button on:click={toggleCategory}>Switch Categories</button>
<Todos todos={categoryTodos} {toggleTodo}>
</Todos>
Todos.svelte:
<script>
import { slide } from 'svelte/transition';
export let todos;
export let toggleTodo;
$: complete = todos.filter(t => t.done);
$: incomplete = todos.filter(t => !t.done);
</script>
<h1>Incomplete</h1>
<ul>
{#each incomplete as {id, name} (id)}
<li transition:slide|local on:click={() => toggleTodo(id)}>{name}</li>
{/each}
</ul>
<h1>Complete</h1>
<ul>
{#each complete as {id, name} (id)}
<li transition:slide|local on:click={() => toggleTodo(id)}>{name}</li>
{/each}
</ul>
Update
This key feature is now part of Svelte since v3.28.0 (see issue).
The syntax is the follwing:
{#key expression}...{/key}
Previous answer
In React, you would use the key prop to make the renderer recreates an element that could have been reused (same tag, etc.).
// React
<Todos items={items} key={currentCategory} />
But Svelte doesn't support key, does it? Well, somewhat. Svelte does have an equivalent feature, but only in {#each ...} blocks.
The syntax is this (docs -- this precise syntax is not mentioned in the docs, but I guess it has just been forgotten):
{#each expression as name (key)}...{/each}
Like in React, the component will be recreated when the value of the key changes (and reused otherwise).
And soooo...
<script>
export let allItems
export let currentCategory
$: items = allItems.filter(x => x.category === currentCategory)
</script>
{#each [items] as todos (currentCategory)}
<Todos {todos} />
{/each}
Hu hu. Right?
Using currentCategory as a key will create a new <Todos /> component each time the cattegory changes, which is probably what you want in your case.
Like in React, the value of the key must be chosen wisely to recreate every time is needed, but not more (or it would kill the desired inter-item transition in your case).
The value of the key is not limited to the currently evaluated item in the each loop. It can come from anywhere in the scope in Svelte, so you can get creative. It could even be an inline object {} which would recreate... Well, basically all the time!
Edit
You can turn the hack into its own component, for cleaner syntax in the consumers:
<!-- Keyed.svelte -->
<script>
export let key
// we just need a 1-length array
const items = [0]
</script>
{#each items as x (key)}
<slot />
{/each}
Use like this:
<script>
import Keyed from './Keyed.svelte'
export let items
export let category
</script>
<Keyed key={category}>
<Todos {items} />
</Keyed>
See example in Svelte's REPL.
I am using material UI and react-sticky and its working good but i got one issue.
https://codesandbox.io/s/xv41xzvyp
I have shared what i have tried yet.
Step to reproduce
Go to bottom of first tab and tab will stick
Try to go another tab but content will stay there it need to scroll to start position and
The StickyContainer component has a property node that is a ref to the topmost element of the container, so you can scroll that into view with the help of a ref of your own:
class CustomizedTabs extends React.Component {
ref = React.createRef();
state = {
value: 0
};
handleChange = (event, value) => {
this.setState({ value }, () => this.ref.current.node.scrollIntoView());
};
render() {
const { classes } = this.props;
const { value } = this.state;
return (
<div className={classes.root}>
<StickyContainer ref={this.ref}>{/* ... */}</StickyContainer>
</div>
);
}
}