Skip to content

Instantly share code, notes, and snippets.

@chukwumaijem
Created May 4, 2020 23:33
Show Gist options
  • Save chukwumaijem/33e5c00ca8ef3c093e42cac5149cd945 to your computer and use it in GitHub Desktop.
Save chukwumaijem/33e5c00ca8ef3c093e42cac5149cd945 to your computer and use it in GitHub Desktop.

Revisions

  1. Chukwuma Ezumezu created this gist May 4, 2020.
    16 changes: 16 additions & 0 deletions App.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,16 @@
    import React from 'react';
    import ReactDOM from 'react-dom';
    import { Provider } from 'react-redux';

    import * as serviceWorker from './serviceWorker';
    import Todos from './Todos/Todos';
    import { store } from './store';

    ReactDOM.render(
    <Provider store={store}>
    <Todos />
    </Provider>,
    document.getElementById('root')
    );

    serviceWorker.unregister();
    50 changes: 50 additions & 0 deletions Todo.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,50 @@
    import React from 'react';
    import { connect } from 'react-redux';

    import { deleteTodo, updateTodo } from './TodosSlice';
    import { REMOVE_TODO, UPDATE_TODO } from './queries';

    function Todo({ todo, deleteTodoAction, updateTodoAction }) {
    const { id, text, completed } = todo;

    const handleTodoDelete = () => {
    deleteTodoAction(REMOVE_TODO(id));
    };

    const handleTodoUpdate = () => {
    updateTodoAction(UPDATE_TODO(id));
    };

    return (
    <div
    className={`
    row
    card-body
    list-group-item-${completed ? 'success' : 'info'}
    `}
    >
    <span className="col-sm-8">{text}</span>
    <button
    onClick={handleTodoUpdate}
    className={`col-sm-2 btn btn-${completed ? 'success' : 'info'}`}
    type="button"
    >
    {completed ? 'Done' : 'Pending'}
    </button>
    <button
    onClick={handleTodoDelete}
    className="col-sm-2 btn btn-danger"
    type="button"
    >
    Remove
    </button>
    </div>
    );
    }

    const mapDispatchToProps = {
    deleteTodoAction: deleteTodo,
    updateTodoAction: updateTodo,
    };

    export default connect(null, mapDispatchToProps)(Todo);
    63 changes: 63 additions & 0 deletions Todos.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,63 @@
    import React, { useEffect, useState } from 'react';
    import { connect } from 'react-redux';

    import { fetchTodos, createTodo } from './TodosSlice';
    import { READ_TODOS, CREATE_TODO } from './queries';
    import Todo from './Todo';

    function Todos({ todos, fetchTodosActions, createTodoAction }) {
    const [newTodo, setNewTodo] = useState('');
    useEffect(() => {
    fetchTodosActions(READ_TODOS);
    // eslint-disable-next-line
    }, []);

    const handleTodoSubmit = (e) => {
    e.preventDefault();
    createTodoAction(CREATE_TODO(newTodo));
    setNewTodo('');
    };

    const handleTodoChange = (e) => {
    setNewTodo(e.target.value);
    };

    return (
    <div className="container-sm">
    <h4>Todo App</h4>
    <form onSubmit={handleTodoSubmit}>
    <div className="form-group row">
    <input
    className="form-control col-sm-10"
    type="text"
    placeholder="Enter todo"
    value={newTodo}
    onChange={handleTodoChange}
    />
    <button className="btn btn-primary col-sm-2" type="submit">
    Submit
    </button>
    </div>
    </form>

    <div className="card">
    {todos.map((todo) => (
    <Todo todo={todo} key={todo.id} />
    ))}
    </div>
    </div>
    );
    }

    const mapStateToProps = (state) => {
    return {
    todos: state.todos,
    };
    };

    const mapDispatchToProps = {
    fetchTodosActions: fetchTodos,
    createTodoAction: createTodo,
    };

    export default connect(mapStateToProps, mapDispatchToProps)(Todos);
    12 changes: 12 additions & 0 deletions api.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,12 @@
    import axios from 'axios';

    const API_URL = process.env.REACT_APP_GRAPHQL_URL;

    export const apiRequest = async (query) => {
    try {
    const { data: { data } } = await axios.post(API_URL, query);
    return data;
    } catch (error) {
    return { error };
    }
    };
    31 changes: 31 additions & 0 deletions queries.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,31 @@
    export const READ_TODOS = `
    query todos {
    todos {
    id
    text
    completed
    }
    }
    `;

    export const CREATE_TODO = (text) => `
    mutation {
    createTodo(text: "${text}") {
    id
    text
    completed
    }
    }
    `;

    export const REMOVE_TODO = (id) => `
    mutation {
    removeTodo(id: "${id}")
    }
    `;

    export const UPDATE_TODO = (id) => `
    mutation {
    updateTodo(id: "${id}")
    }
    `;
    7 changes: 7 additions & 0 deletions store.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,7 @@
    import { configureStore } from '@reduxjs/toolkit';

    import todosReducer from './Todos/TodosSlice';

    export const store = configureStore({
    reducer: todosReducer,
    });
    113 changes: 113 additions & 0 deletions todo.slice.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,113 @@
    import { createSlice } from '@reduxjs/toolkit';
    import { apiRequest } from '../api';

    const todosSlice = createSlice({
    name: 'todos',
    initialState: {
    todos: [],
    loading: 'idle',
    },
    reducers: {
    createTodoRequest(state) {
    if (state.loading === 'idle') {
    state.loading = 'pending';
    }
    },
    createTodoResponse(state, action) {
    if (state.loading === 'pending') {
    state.loading = 'idle';
    state.todos.push(action.payload);
    }
    },
    fetchTodoRequest(state) {
    if (state.loading === 'idle') {
    state.loading = 'pending';
    }
    },
    fetchTodoResponse(state, action) {
    if (state.loading === 'pending') {
    state.loading = 'idle';
    state.todos = action.payload;
    }
    },
    updateTodoRequest(state) {
    if (state.loading === 'idle') {
    state.loading = 'pending';
    }
    },
    updateTodoResponse(state, action) {
    if (state.loading === 'pending') {
    state.loading = 'idle';
    const updatedTodoIndex = state.todos.findIndex(
    (todo) => todo.id === action.payload
    );
    const updatedTodo = state.todos[updatedTodoIndex];
    state.todos[updatedTodoIndex].completed = !updatedTodo.completed;
    }
    },
    deleteTodoRequest(state) {
    if (state.loading === 'idle') {
    state.loading = 'pending';
    }
    },
    deleteTodoResponse(state, action) {
    if (state.loading === 'pending') {
    state.loading = 'idle';
    state.todos = state.todos.filter((todo) => todo.id !== action.payload);
    }
    },
    },
    });

    const {
    fetchTodoRequest,
    fetchTodoResponse,
    createTodoRequest,
    createTodoResponse,
    updateTodoRequest,
    updateTodoResponse,
    deleteTodoRequest,
    deleteTodoResponse,
    } = todosSlice.actions;

    export default todosSlice.reducer;

    export const createTodo = (query) => async (dispatch) => {
    dispatch(createTodoRequest());
    const body = {
    query,
    };

    const { createTodo: newTodo } = await apiRequest(body);
    dispatch(createTodoResponse(newTodo));
    };

    export const fetchTodos = (query) => async (dispatch) => {
    dispatch(fetchTodoRequest());
    const body = {
    query,
    };

    const { todos } = await apiRequest(body);
    dispatch(fetchTodoResponse(todos));
    };

    export const updateTodo = (query, variables = {}) => async (dispatch) => {
    dispatch(updateTodoRequest());
    const body = {
    query,
    };

    const { updateTodo: updatedTodoId } = await apiRequest(body);
    dispatch(updateTodoResponse(updatedTodoId));
    };

    export const deleteTodo = (query) => async (dispatch) => {
    dispatch(deleteTodoRequest());
    const body = {
    query,
    };

    const { removeTodo: removedTodoId } = await apiRequest(body);
    dispatch(deleteTodoResponse(removedTodoId));
    };