Skip to content

Instantly share code, notes, and snippets.

@chukwumaijem
Created May 4, 2020 23:33
Show Gist options
  • Select an option

  • Save chukwumaijem/33e5c00ca8ef3c093e42cac5149cd945 to your computer and use it in GitHub Desktop.

Select an option

Save chukwumaijem/33e5c00ca8ef3c093e42cac5149cd945 to your computer and use it in GitHub Desktop.
Todo App - Redux with GraphQL API
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 };
}
};
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();
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}")
}
`;
import { configureStore } from '@reduxjs/toolkit';
import todosReducer from './Todos/TodosSlice';
export const store = configureStore({
reducer: todosReducer,
});
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);
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));
};
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);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment