Gaurang Dhorda
Gaurang Dhorda

Reputation: 3387

Apply css Animation to Div when Array.map added or remove div in react.js

For reference, check out this StackBlitz Link

I have css animation @keyframes class..

 /*
    Animation classes.
 */

.example-enter {
   animation: flasher 0.5s;
}

@keyframes flasher{
   from {
      opacity: 0.2;
   }
   to {
     opacity: 1;
   }
}

I am applying this .example-enter css class to div when array.map is having new added data. like below..

<div className="example-enter"> ... </div>

The question is when I added new Array to state, react will create new div, and gives animation to last added div only. But I want very first element to be animated, not last one when added new state to array.

You can check out demo in above stackblitz link to check out more details and see there when we add new list only last div is animated but i want to animate very first element to animate because that first div element is i added to array. using.. setTodoState( prev => [newTodoState, ...prev ]);

Upvotes: 1

Views: 3933

Answers (2)

bertdida
bertdida

Reputation: 5288

I guess the problem is on this line, you're using index as key for your LineItem.

todoState.map((item, index) => <LineItem key={index} todoList={item} index={index}/>)

React does not recomment using indexes for keys, read more here.


You need to have some sort of id to uniquely identify your todo items and use that id as key when you do your todoState.map((item) => .... So you'll have to make your todo item as an object and not a string.

In your TodoForm, we'll add a generateId and use this functions return value for our new todo's id.

TodoForm.tsx

function generateId() {
  // https://stackoverflow.com/a/8084248/8062659
  return Math.random().toString(36).substring(7);
}
const sendTodoItem = (e) => {
  if(todoInput.trim().length > 0){
    const text = todoInput.trim();
    const id = generateId();

    addNewTodoState({ text , id }); // notice here
    setIsDisabled(false);
    setTodoInput('');
  }else{
    setIsDisabled(true);
  }
}

Now on you TodoListLineItem, we'll remove the index from map and use the current todo's id as key.

TodoListLineItem.tsx

todoState.map((item) => <LineItem key={item.id} todoList={item}/>)

We also have to update your Line-Item to display the text property of the current todo.

Line-Item.tsx

<p>{todoList.text}</p>

Check this link for a demo.

--

Because we're using now an object as a todo item, I believe you should also make changes on some parts of the app, like the model and the delete feature. But the changes above should be sufficient to achieve what you want as described on your post.

Upvotes: 2

henrik hildebrand
henrik hildebrand

Reputation: 71

A simple approach is to add the animation class with useEffect and remove it after the duration of the animation.

import React, {useEffect, useState} from 'react'

const YourApp = props => {
 
  const [animate, setAnimate] = useState(false)
  const [todoState, setTodoState] = useState([])

  useEffect(() => {
    setAnimate( true )
    setTimeout( () => { setAnimate( false ) }, 500 )
  }, [todoState])


  return <div className={ animate ? "example-enter" : null } > ... </div>
}

Each time you update todoState, it will apply the animation class for 0.5s basically.

Upvotes: 2

Related Questions