Skip to content

Instantly share code, notes, and snippets.

@mmyoji
Last active January 13, 2021 13:52
Show Gist options
  • Save mmyoji/5aa41f0d1f631ac4ff48de845c0aae3c to your computer and use it in GitHub Desktop.
Save mmyoji/5aa41f0d1f631ac4ff48de845c0aae3c to your computer and use it in GitHub Desktop.
react-redux hooks example in a single file
/**
* @see https://react-redux.js.org/api/hooks
*
* This code is just a sample code for learning
* and several expected features are lacked.
*/
import { ChangeEvent, KeyboardEvent } from "react";
import { createStore, Reducer } from "redux";
import { Provider, useDispatch, useSelector } from "react-redux";
type Task = {
id: number;
title: string;
done: boolean;
};
type State = {
tasks: Task[];
newTask: string;
};
/**
* or you can create actions (action creators) separately
*
* function changeTask(text: string) {
* return {
* type: "CHANGE_TASK",
* text,
* };
* }
*
* type Action = ReturnType<changeTask> | ReturnType<createTask> | ...;
*/
type Action =
| {
type: "CHANGE_TASK";
text: string;
}
| {
type: "CREATE_TASK";
text: string;
}
| {
type: "UPDATE_TASK";
id: number;
text: string;
}
| {
type: "TOGGLE_DONE";
id: number;
};
const initialState = {
tasks: [],
newTask: "",
};
const reducer: Reducer<State, Action> = (state = initialState, action) => {
switch (action.type) {
case "CHANGE_TASK":
return {
...state,
newTask: action.text,
};
case "CREATE_TASK":
return {
...state,
tasks: [
{
id: state.tasks.length + 1,
title: action.text,
done: false,
},
...state.tasks,
],
};
case "UPDATE_TASK":
return {
...state,
tasks: [
...state.tasks.map((task) => {
if (task.id !== action.id) {
return task;
}
return { ...task, text: action.text };
}),
],
};
case "TOGGLE_DONE":
return {
...state,
tasks: [
...state.tasks.map((task) => {
if (task.id !== action.id) {
return task;
}
return { ...task, done: !task.done };
}),
],
};
default:
return state;
}
};
function NewTask() {
const dispatch = useDispatch();
const newTask = useSelector<State, State["newTask"]>(
(state) => state.newTask
);
function handleChange(event: ChangeEvent<HTMLInputElement>) {
dispatch({ type: "CHANGE_TASK", text: event.target.value });
}
function handleKeyPress(event: KeyboardEvent<HTMLInputElement>) {
if (event.key !== "Enter" || newTask.trim() === "") {
return;
}
dispatch({ type: "CREATE_TASK", text: newTask.trim() });
dispatch({ type: "CHANGE_TASK", text: "" });
}
return (
<div>
<h2>New Task</h2>
<input
type="text"
value={newTask}
onChange={handleChange}
onKeyPress={handleKeyPress}
/>
</div>
);
}
function TaskItem({ id, title, done }: Task) {
return (
<li>
{done ? "✅" : ""} {id}: {title}
</li>
);
}
function TaskList() {
const tasks = useSelector<State, State["tasks"]>((state) => state.tasks);
return (
<ul>
{tasks.map((task) => (
<TaskItem key={task.id} {...task} />
))}
</ul>
);
}
function App() {
const store = createStore(reducer);
return (
<Provider store={store}>
<NewTask />
<TaskList />
</Provider>
);
}
export default App;
@mmyoji
Copy link
Author

mmyoji commented Jan 13, 2021

  • useDispatch and useSelector can be used inside components under Provider.
  • useSelector is used in order to get state from the store.

Refactoring Plan: Prepare custom hook for NewTask component

function useNewTask() {
  const dispatch = useDispatch();

  const newTask = useSelector<State, State["newTask"]>(
    (state) => state.newTask
  );

  function handleChange(event: ChangeEvent<HTMLInputElement>) {
    dispatch({ type: "CHANGE_TASK", text: event.target.value });
  }

  function handleKeyPress(event: KeyboardEvent<HTMLInputElement>) {
    if (event.key !== "Enter" || newTask.trim() === "") {
      return;
    }

    dispatch({ type: "CREATE_TASK", text: newTask.trim() });
    dispatch({ type: "CHANGE_TASK", text: "" });
  }

  return {
    newTask,
    handleChange,
    handleKeyPress,
  };
}

function NewTask() {
  const { newTask, handleChange, handleKeyPress } = useNewTask();

  return (
    // ...
  );
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment