How can I add a slide up effect to deleting a record in Svelte?

1.4k views Asked by At

I am working on a small Svelte application, for learning purposes (Im new to Svelte).

The application displays a JSON of users from randomuser.me API in a Bootstrap 4 table.

I have this deleteUser() method

const deleteUser = (uid) => {
    let itemIdx = filteredUsers.findIndex(x => x.id == uid);
    filteredUsers.splice(itemIdx,1);
    filteredUsers = filteredUsers;
}

I want to add an upwards animation similar to the one jQuery's slideUp() provides.

Adding this fly transition on the table row <tr transition:fly="{{y:-100, duration:200}}"> does not get the desired result (although the effect I got is beautiful), as it is visible in this REPL.

1

There are 1 answers

2
Kokizzu On BEST ANSWER

There's 2 steps need to be done

  1. add (user) at the end of {#each -- so the loop associate the generated element with the user object on respective loop

  2. replace deleteUser(user.id) with deleteUser(user) and update the implementation -- bugfix because user doesn't have id?

const deleteUser = (user) => {
    let itemIdx = filteredUsers.findIndex(x => user == x);
    filteredUsers.splice(itemIdx,1);
    filteredUsers = filteredUsers;
}

so the end code would be something like this:

<script>
    import { onMount } from "svelte";
    import { fade, fly } from 'svelte/transition';
    import { flip } from 'svelte/animate';

    const apiURL = "https://randomuser.me/api/";
    let stringToMatch = '';
    let users = [];
    let filteredUsers = [];
    $:filteredUsers = users;
    
        onMount(() => {
            getUsers();
            filterUsers();
        });
    
    const   getUsers = () => {
        let getFrom = "&results=20&inc=name,location,email,cell,picture";
        fetch(`${apiURL}?${getFrom}`)
            .then(res => res.json())
            .then((data) => users = data.results);
    };
    
    const filterUsers = () => {
        filteredUsers = users;

        if(stringToMatch){
            filteredUsers = users.filter(user => {
                return user.name.first.toLowerCase().includes(stringToMatch.toLowerCase())
                    || user.name.last.toLowerCase().includes(stringToMatch.toLowerCase())
                    || user.location.city.toLowerCase().includes(stringToMatch.toLowerCase());
            });
        }
    }
    
    const deleteUser = (user) => {
        let itemIdx = filteredUsers.findIndex(x => x == user);
        filteredUsers.splice(itemIdx,1);
        filteredUsers = filteredUsers;
    }
</script>

<div class="container-fluid">
    <div class="card bg-light mb-2 overflow-hidden">
        <div class="card-header px-2 py-0 d-flex">
            <h6 class="text-dark m-0 align-self-center">Members Area</h6>
        </div>
    <div class="card-body bg-white p-0">
                <div class="search p-2">
                    <input class="form-control" type="text" placeholder="Search..." bind:value="{stringToMatch}" on:input="{filterUsers}">
                </div>
            {#if filteredUsers.length > 0}
                <table class="table users-table m-0">
                    <thead>
                        <tr>
                            <th class="text-right">#</th>
                            <th>Name</th>
                            <th>Lives in</th>
                            <th class="text-right">Actions</th>
                        </tr>
                    </thead>
                    <tbody>
                        {#each filteredUsers as user,index (user)}
                            <tr transition:fly="{{y:-100, duration:200}}">
                                <td class="text-right">{index + 1}</td>
                                <td>
                                    <img src="{user.picture.thumbnail}" alt="{user.name.first} {user.name.last}" class="rounded-circle">    
                                    {user.name.first} {user.name.last}
                                </td>
                                <td>{user.location.city}</td>
                                <td class="text-right">
                                    <button class="btn btn-sm btn-secondary" on:click="{deleteUser(user)}">
                                        <i class="fa fa-trash" aria-hidden="true"></i> 
                                    </button>
                                </td>
                            </tr>
                        {/each}
                    </tbody>
                </table>
            {:else}
                <p class="text-center text-muted my-3">No members found</p>
            {/if}
        </div>
    </div>
</div>

<style>
    
    .card-header h6 {
        padding: 10px 0;
    }
    
    .users-table th:first-child {
        width: 30px;
    }
    
    .users-table td {
        padding-top: .25rem;
        padding-bottom: .25rem
    }
    
    .users-table img {
        max-height: 32px;
        width: auto;
        border: 1px solid #c7c7c7;
        margin-right: 10px;
    }
</style>

<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
<link href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" crossorigin="anonymous">

Have a look to the REPL.