React Todo with Firebase moving actions to redux - reactjs

I am using react +redux+typescript+firebase for a simple todo application. How to move all actions from components to store if i use react hooks(without other third party libraries, if it is possible).
Components/Todo.tsx:
import React from 'react';
import {connect} from 'react-redux';
import Switch from '#material-ui/core/Switch';
import IconButton from '#material-ui/core/IconButton';
import DeleteIcon from '#material-ui/icons/Delete';
import "./Todo.scss";
import {todosRef} from "../../firebase/firebase";
const Todo = (props: any) => {
const classes = [''];
const { todo } = props;
const updateTodo = () => {
todosRef.child(todo.id).set({...todo,done:!todo.done})
}
if (todo.done) {
classes.push('_completed');
}
return (
<div className="Todo">
<Switch
edge="end" checked={todo.done} onChange={updateTodo}
inputProps={{ "aria-labelledby": "switch-list-label-bluetooth" }}
/>
<p className={classes.join(' ')}>{todo.task}</p>
<IconButton aria-label="delete" onClick={e => todosRef.child(todo.id).remove()}>
<DeleteIcon fontSize="large" />
</IconButton>
</div>
);
}
export default connect()(Todo);
components/TodoForm.tsx
import React, { useState } from "react";
import TextField from '#material-ui/core/TextField';
import {todosRef} from "../../firebase/firebase";
const TodoForm:React.FC = () => {
const [title, setTitle] = useState<string>("");
const createTodo = (e: React.FormEvent<EventTarget>) => {
e.preventDefault();
const item = {
task: title,
done: false,
};
todosRef.push(item);
setTitle("");
};
return (
<form onSubmit={createTodo}>
<TextField
style={{ width: "100%" }}
id="outlined-basic"
value={title}
onChange={(e:React.ChangeEvent<HTMLInputElement>) => setTitle(e.target.value)}
label="Write task"
variant="outlined"
/>
</form>
);
}
export default TodoForm;
components/TodoList.tsx:
import React, {useState, useEffect} from "react";
import Todo from "../Todo/Todo";
import Divider from '#material-ui/core/Divider';
import {todosRef} from "../../firebase/firebase";
const TodoList:React.FC = () => {
const [todos,setTodos] = useState<any>([]);
useEffect(() => {
todosRef.on('value', (snapshot) => {
let items = snapshot.val();
let newState = [];
for (let item in items) {
newState.push({
id: item,
task: items[item].task,
done: items[item].done
});
}
setTodos(newState)
});
},[])
return (
<>
{todos.map((todo: any, i: number) => (
<React.Fragment key={todo.id}>
<Todo todo={todo} />
{i<todos.length -1 && <Divider />}
</React.Fragment>
))}
</>
);
}
export default TodoList;
actions/index.ts:
export const fetchTodos = (todo:any) => {
return {
type: 'FETCH_TODOS',
payload: todo,
}
}
export const addToDo = (setTodos:any) => {
return {
type: 'ADD_TODO',
payload: setTodos,
}
}
export const updateTodo = (todo:any) => {
return {
type: 'UPDATE_TODO',
payload: todo,
}
}
export const removeTodo = (todo:any) => {
return {
type: 'REMOVE_TODO',
payload: todo,
}
}
I started describing the actions, but I don't know exactly what to write in the context of the store.
const initialState = {
todos: [],
setTodos: [],
}
export default (state = initialState, action:any) => {
switch (action.type) {
case FETCH_TODOS:
return {
...state,
todo:action.payload
};
case REMOVE_TODO:
...
default:
return state;
...
I will also be grateful if you can indicate with a couple of examples (instead of any) which types I should use, since I'm still new to typescript

Related

How can I display the result of the child component in the parent component?

my application consists of a filter select by provinces, the filter is a modal that shows me the result in the filter component (child), I would like to show the result in the parent not in the filter modal. I do not know what to pass to the parent to show the result or how to show it on the screen.
///parent component
import React, { useState, useEffect } from 'react'
import { useDispatch } from "react-redux";
import { getClinic } from '../../api/drupalAPI'
import {Clinic} from '#icofcv/common';
import { selectClinics } from '../../actions/detailClinics'
import { useNavigate } from "react-router-dom";
import contentUtils from '../../lib/contentUtils'
import { SearchFilterClinics } from './SearchFilterClinics'
import Button from 'react-bootstrap/Button';
import Form from 'react-bootstrap/Form';
const ClinicList = () => {
const [clinicList, setClinicList] = useState<Clinic[]>([]);
const [clinicListFiltered, setClinicListFiltered] = useState<Clinic[]>([]);
const [searchClinic, setSearchClinic] = useState("");
const dispatch = useDispatch();
const navigate = useNavigate();
///modal control
const [isOpen, setIsOpen] = useState(false);
const openModal = () => setIsOpen(true);
const closeModal = () => setIsOpen(false);
console.log(searchClinic);
const fetchClinicList = async () => {
getClinic().then((response)=>{
console.log(response)
setClinicList(response);
setClinicListFiltered(response)
}).catch ( (error) => {
console.error(error);
throw error;
});
}
const handleChange=e=>{
setSearchClinic(e.target.value);
filter(e.target.value);
}
const filter=(termSearch)=>{
const resultSearch= clinicList.filter((element)=>{
if(element.title.toString().toLowerCase().includes(termSearch.toLowerCase())
){
return element;
}
});
setClinicListFiltered(resultSearch);
}
function handleAddToDetail(clinic) {
dispatch(selectClinics(clinic));
navigate('clinicdetail');
}
function goToPageSearchFilterClinics() {
navigate('filterclinics');
}
useEffect (() => {
fetchClinicList();
}, []);
return(
<>
<div style={{display: 'flex'}}>
<div style={{width:'5rem'}}>
{/* <button onClick={() => goToPageSearchFilterClinics()}>filtro</button> */}
<button onClick={openModal}>filtro</button>
</div>
< SearchFilterClinics isOpen={isOpen} closeModal={closeModal} clinicFilter={clinicFilter}></SearchFilterClinics>
<Form className="d-flex">
<Form.Control
type="search"
value={searchClinic}
placeholder="Search"
className="me-2"
aria-label="Search"
onChange={handleChange}
/>
</Form>
</div>
<div className="content-cliniclist">
{
clinicListFiltered.map((clinic) => (
<div style={{marginBottom: '3rem'}}>
<button
type="button"
onClick={() => handleAddToDetail(clinic)}
style={{all: 'unset'}}
>
<div>
{/* <img src={ contentUtils.getLargeImageUrl(clinic.logoWidth )} alt="#"></img> */}
<div>{clinic.title}</div>
<div>{clinic.propsPhone}</div>
<div>{clinic.mobile}</div>
<div>{clinic.email}</div>
<div>{clinic.registry}</div>
</div>
</button>
</div>
))
}
</div>
</>
)
}
export default ClinicList;
////child component
import React, { useState, useEffect } from 'react'
import Select, { SingleValue } from 'react-select'
import { getClinic } from '../../api/drupalAPI'
import {Clinic} from '#icofcv/common';
import "./Modal.css";
interface Props {
isOpen: boolean,
clinicFilter: String,
closeModal: () => void
}
export const SearchFilterClinics : React.FC<Props> = ({ children, isOpen, closeModal, clinicFilter }) => {
////filter
type OptionType = {
value: string;
label: string;
};
const provincesList: OptionType[] = [
{ value: 'Todos', label: 'Todos' },
{ value: 'Valencia', label: 'Valencia' },
{ value: 'Castellon', label: 'Castellon' },
{ value: 'Alicante', label: 'Alicante' },
]
const [clinicList, setClinicList] = useState<Clinic[]>([]);
const [clinicListFilteredSelect, setClinicListFilteredSelect] = useState<Clinic[]>([]);
const [filterSelectClinic, setFilterSelectClinic] = useState<SingleValue<OptionType>>(provincesList[0]);
const handleChangeSelect = async (provinceList: SingleValue<OptionType>) => {
getClinic().then((response) => {
setClinicList(response);
setClinicListFilteredSelect(response)
setFilterSelectClinic(provinceList);
filterSelect(provinceList );
}).catch ((error) => {
console.error(error);
throw error;
});
}
const filterSelect=(termSearch)=>{
const resultFilterSelect = clinicList.filter((element) => {
if(element.province?.toString().toLowerCase().includes(termSearch.value.toLowerCase() )
){
return element;
}
});
setClinicListFilteredSelect(resultFilterSelect);
}
const handleModalContainerClick = (e) => e.stopPropagation();
return (
<>
<div className={`modal ${isOpen && "is-open"}`} onClick={closeModal}>
<div className="modal-container" onClick={handleModalContainerClick}>
<button className="modal-close" onClick={closeModal}>x</button>
{children}
<div>
<h1>Encuentra tu clínica</h1>
</div>
<div>
<form>
<label>Provincia</label>
<Select
defaultValue={filterSelectClinic}
options={provincesList}
onChange={handleChangeSelect}
/>
</form>
{
clinicListFilteredSelect.map((clinicFilter) => (
<div>
<div>{clinicFilter.title}</div>
<div>{clinicFilter.propsPhone}</div>
<div>{clinicFilter.mobile}</div>
<div>{clinicFilter.email}</div>
<div>{clinicFilter.province} </div>
<div>{clinicFilter.registry}</div>
</div>
))
}
</div>
</div>
</div>
</>
)
}
You need to pass a callback function to the parent and set the state value in the parent, not in the child. (This is known as lifting state up.)
In your case this would involve moving
const [filterSelectClinic, setFilterSelectClinic] = useState<SingleValue<OptionType>>(provincesList[0]);
Into the parent component. Then passing the setFilterSelectClinic function into the child component.
<SearchFilterClinics setFilterSelectClinic={(value) => setFilterSelectClinic(value)} isOpen={isOpen} closeModal={closeModal} clinicFilter={clinicFilter}/>
The value (within the parenthesis) is being passed up by the child component. This is the value you set here:
getClinic().then((response) => {
...
// this is the function we pass in from the parent. It set's the value
// of the callback function to provinceList
setFilterSelectClinic(provinceList);
...
We then setFilterSelectClinic to that value. Meaning filterSelectClinic now has the value passed up in the callback.

How can I pass the value of task id in this example to Chakra UI modal to update the task title?

I am learning to build a to-do app in Nextjs + Typescript. I have a Chakra UI modal which pops up when the user clicks on the "edit task" button. The modal contains a Input element and it should have the value of task title. Here is the solution that I have implemented.
// components/tasks.tsx
import { Box } from '#chakra-ui/react';
import { NextPage } from 'next';
import Task from '../components/task';
import EditTaskModal from '../components/edit-task-modal';
import { useState } from 'react';
import { useModalContext } from '../store/modal';
const Tasks: NextPage = () => {
const { onOpen } = useModalContext();
const [editTaskID, setEditTaskID] = useState('');
const handleOpenModal = (id: string) => {
setEditTaskID(id);
onOpen();
};
return (
<>
<EditTaskModal id={editTaskID} />
<Box>
<Task handleOpenModal={handleOpenModal} />
</Box>
</>
);
};
export default Tasks;
// components/task.tsx
import { NextPage } from 'next';
import { Box, Checkbox, Button, Text } from '#chakra-ui/react';
import { useTaskContext } from '../store/tasks';
import { useModalContext } from '../store/modal';
type TaskProps = {
handleOpenModal: (id: string) => void;
};
const Task: NextPage<TaskProps> = ({ handleOpenModal }) => {
const { tasks } = useTaskContext();
const { onOpen } = useModalContext();
return (
<Box>
{tasks.map((task) => {
return (
<Box
key={task.id}
border="3px solid"
borderColor="gray.200"
marginBottom="1em"
p="1em"
borderRadius="5px"
display="flex"
alignItems="center"
justifyContent="space-between"
>
<Text key={task.id}>{task.title}</Text>
<Box width="35%" display="flex" justifyContent="space-between">
<Button
size="sm"
onClick={() => {
handleOpenModal(task.id);
}}
>
Edit
</Button>
<Button size="sm" colorScheme="red">
Delete
</Button>
</Box>
</Box>
);
})}
</Box>
);
};
export default Task;
// components/edit-task-modal.tsx
import { NextPage } from 'next';
import {
Button,
Modal,
ModalOverlay,
ModalContent,
ModalHeader,
ModalCloseButton,
ModalBody,
FormControl,
Input,
ModalFooter,
} from '#chakra-ui/react';
import { useModalContext } from '../store/modal';
import { useEffect, useState } from 'react';
import { useTaskContext } from '../store/tasks';
type EditTaskModalProps = {
id: string;
};
type updateTaskType = { id: string; title: string; status: boolean };
const updateTask = async (task: updateTaskType) => {
try {
const response = await fetch('http://localhost:3000/api/tasks', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(task),
});
} catch (error) {
console.log(error);
}
};
const EditTaskModal: NextPage<EditTaskModalProps> = ({ id }) => {
const { tasks } = useTaskContext();
const task = tasks.find((task) => {
return task.id === id;
});
const [title, setTitle] = useState(task?.title);
const { isOpen, onClose } = useModalContext();
return (
<Modal isOpen={isOpen} onClose={onClose}>
<ModalOverlay />
<ModalContent>
<ModalHeader>Update Task Name</ModalHeader>
<ModalCloseButton />
<ModalBody pb={6}>
<FormControl>
<Input
placeholder="Task Name"
value={title}
onChange={(e) => {
setTitle(e.target.value);
}}
/>
</FormControl>
</ModalBody>
<ModalFooter>
<Button
colorScheme="blue"
mr={3}
onClick={() => {
if (task !== undefined)
updateTask({
title: task.title,
status: task.status,
id: task.id,
});
}}
>
Update
</Button>
<Button onClick={onClose}>Cancel</Button>
</ModalFooter>
</ModalContent>
</Modal>
);
};
export default EditTaskModal;
// store/modal.tsx
import React, { createContext, useContext } from 'react';
import { useDisclosure } from '#chakra-ui/react';
const ModalContext = createContext<any>(null);
const ModalProvider = ({ children }: { children: React.ReactNode }) => {
const { onOpen, onClose, isOpen } = useDisclosure();
return (
<ModalContext.Provider value={{ onOpen, onClose, isOpen }}>
{children}
</ModalContext.Provider>
);
};
const useModalContext = () => {
return useContext(ModalContext);
};
export { ModalProvider, useModalContext };
However, I noticed that the value attribute in Input element is always one state update behind for some reason and I can't figure out why. Is there a better solution to pass task id to the modal?

mapDispatchToProps dispatch action working for only one function

I have two functions that do about the same thing only in a different array in the state
For no apparent reason only one of them works
Only the addToCart function works
I can not understand why the addToWishList function does not work
Home component
import React, { useEffect, useState } from "react";
import { makeStyles } from "#material-ui/core/styles";
import Card from "#material-ui/core/Card";
import CardActionArea from "#material-ui/core/CardActionArea";
import CardActions from "#material-ui/core/CardActions";
import CardContent from "#material-ui/core/CardContent";
import CardMedia from "#material-ui/core/CardMedia";
import Button from "#material-ui/core/Button";
import Typography from "#material-ui/core/Typography";
import { useHistory } from "react-router-dom";
import { connect } from "react-redux";
import { addToCart } from ".././redux/Shopping/shopping-action";
import { addToWishList } from ".././redux/Shopping/shopping-action";
const useStyles = makeStyles({
root: {
maxWidth: 345,
},
media: {
height: 240,
},
});
function Home({ addToCart }) {
const classes = useStyles();
const history = useHistory();
const [products, setProducts] = useState([]);
useEffect(() => {
fetch("https://fakestoreapi.com/products")
.then((res) => res.json())
.then((json) => setProducts(json));
}, []);
return (
<div>
<h1>ALL PRODUCTS</h1>
<div className="cards ">
{products.map((product) => {
return (
<div className="card">
<Card className={classes.root}>
<CardActionArea
onClick={() => history.push(`/product/${product.id}`)}
>
<CardMedia className={classes.media} image={product.image} />
<CardContent>
<Typography gutterBottom variant="h5" component="h2">
{product.title}
</Typography>
<Typography
variant="body2"
color="textSecondary"
component="h2"
>
{product.category}
</Typography>
</CardContent>
</CardActionArea>
<CardActions>
<Button
onClick={() => addToCart(product)}
size="small"
variant="contained"
color="primary"
>
ADD TO CART 🛒
</Button>
<Button
onClick={() => addToWishList(product)}
size="small"
variant="contained"
color="secondary"
>
Add to wish list ❤️
</Button>
</CardActions>
</Card>
</div>
);
})}
</div>
</div>
);
}
const mapDispatchToProps = (dispatch) => {
return {
addToCart: (product) => dispatch(addToCart(product)),
addToWishList: (product) => dispatch(addToWishList(product)),
};
};
export default connect(null, mapDispatchToProps)(Home);
shopping-action.js
import * as actionTypes from "./shopping-types";
export const addToCart = (item) => {
return {
type: actionTypes.ADD_TO_CART,
payload: {
item: item,
},
};
};
export const removeFromCart = (itemId) => {
return {
type: actionTypes.REMOVE_FROM_CART,
payload: {
id: itemId,
},
};
};
export const adjustQty = (itemId, value) => {
return {
type: actionTypes.ADJUST_QTY,
payload: {
id: itemId,
qty: value,
},
};
};
export const addToWishList = (item) => {
console.log(item);
return {
type: actionTypes.WISH_LIST,
payload: {
item: item,
},
};
};
shopping-reducer.js
import * as actionTypes from "./shopping-types";
const INITIAL_STATE = {
cart: [],
wishList: [],
};
const shopReducer = (state = INITIAL_STATE, action) => {
console.log(action.type);
switch (action.type) {
case actionTypes.ADD_TO_CART:
const item = action.payload.item;
const isInCart = state.cart.find((item) =>
item.id === action.payload.item.id ? true : false
);
return {
...state,
cart: isInCart
? state.cart.map((item) =>
item.id === action.payload.item.id
? { ...item, qty: item.qty + 1 }
: item
)
: [...state.cart, { ...item, qty: 1 }],
};
case actionTypes.REMOVE_FROM_CART:
return {
...state,
cart: state.cart.filter((item) => item.id !== action.payload.item.id),
};
case actionTypes.ADJUST_QTY:
return {
...state,
cart: state.cart.map((item) =>
item.id === action.payload.item.id
? { ...item, qty: action.payload.qty }
: item
),
};
case actionTypes.WISH_LIST:
const itemForWish = action.payload.item;
const isInWish = state.cart.find((item) =>
item.id === action.payload.item.id ? true : false
);
return {
...state,
wishList: isInWish ? null : [...state.wishList, { itemForWish }],
};
default:
return state;
}
};
export default shopReducer;
shopping-types.js
export const ADD_TO_CART = "ADD_TO_CART";
export const REMOVE_FROM_CART = "REMOVE_FROM_CART";
export const ADJUST_QTY = "ADJUST_QTY";
export const WISH_LIST = "WISH_LIST";
Thanks for your time!
You forgot to destructure the addToWishList prop that is injected by the connect HOC, so you are calling directly the action creator and not the one wrapped in a call to dispatch.
import { connect } from "react-redux";
import { addToCart } from ".././redux/Shopping/shopping-action";
import { addToWishList } from ".././redux/Shopping/shopping-action"; // <-- (1) imported
function Home({ addToCart }) { // <-- (3) not destructured
...
return (
<div>
<h1>ALL PRODUCTS</h1>
<div className="cards ">
{products.map((product) => {
return (
<div className="card">
<Card className={classes.root}>
...
<CardActions>
<Button
onClick={() => addToCart(product)}
...
>
ADD TO CART 🛒
</Button>
<Button
onClick={() => addToWishList(product)} // <-- (4) non-wrapped action
...
>
Add to wish list ❤️
</Button>
</CardActions>
</Card>
</div>
);
})}
</div>
</div>
);
}
const mapDispatchToProps = (dispatch) => {
return {
addToCart: (product) => dispatch(addToCart(product)),
addToWishList: (product) => dispatch(addToWishList(product)), // <-- (2) injected
};
};
export default connect(null, mapDispatchToProps)(Home);
To resolve, you should destructure and use the correctly wrapped action.
function Home({ addToCart }) {
Should be
function Home({ addToCart, addToWishList }) {

React, context remove item from cart

I have a demo here
Its a simple cart app where I'm listing products and then adding them to a cart components
It uses context for the app state.
I'd now like to be able to remove items from the cart but I'm struggling to get this to work.
Do I need to add a DeleteCartContext.Provider
import React, { useContext } from "react";
import { CartContext } from "./context";
import { IProduct } from "./interface";
const Cart = () => {
const items = useContext(CartContext);
const handleClick = (
e: React.MouseEvent<HTMLInputElement, MouseEvent>,
item: IProduct
) => {
e.preventDefault();
};
const cartItems = items.map((item, index) => (
<div>
<span key={index}>{`${item.name}: £${item.price}`}</span>
<input type="button" value="-" onClick={e => handleClick(e, item)} />
</div>
));
return (
<div>
<h2>Cart</h2>
{cartItems}
</div>
);
};
export default Cart;
There are many ways to control Items in Context so I tried to answer as similar to your structure, here is how you do that:
import React, { useContext } from "react";
import { CartContext, RemoveCartContext } from "./context";
import { IProduct } from "./interface";
const Cart = () => {
const items = useContext(CartContext);
const removeItem = useContext(RemoveCartContext);
const handleClick = (
e: React.MouseEvent<HTMLInputElement, MouseEvent>,
item: IProduct
) => {
e.preventDefault();
};
const cartItems = items.map((item, index) => (
<div>
<span key={index}>{`${item.name}: £${item.price}`}</span>
<input type="button" value="+" onClick={e => handleClick(e, item)} />
<input type="button" value="-" onClick={e => removeItem(item)} />
</div>
));
return (
<div>
<h2>Cart</h2>
{cartItems}
</div>
);
};
export default Cart;
import React, { createContext, useCallback, useRef, useState } from "react";
export const CartContext = createContext([]);
export const AddCartContext = createContext(item => {});
export const RemoveCartContext = createContext(item => {});
export const CartProvider = ({ children }) => {
const [items, setItems] = useState([]);
const itemsRef = useRef(items);
itemsRef.current = items;
return (
<AddCartContext.Provider
value={useCallback(item => {
setItems([...itemsRef.current, item]);
}, [])}
>
<RemoveCartContext.Provider
value={useCallback(item => {
const newItems = itemsRef.current.filter(
_item => _item.id !== item.id
);
setItems(newItems);
}, [])}
>
<CartContext.Provider value={items}>{children}</CartContext.Provider>
</RemoveCartContext.Provider>
</AddCartContext.Provider>
);
};
and here is a working demo: https://stackblitz.com/edit/react-ts-cart-context-mt-ytfwfv?file=context.tsx

Dispatch redux not executed

I'm trying to submit a form when a user signUp. When the submit button clicked an action creator should executed to start an asynchronous action but actually the submit is not triggered and the action creator is not launched.
actions.ts:
import { ActionTypes } from "./types";
import { SignUpUser, User } from "../apis/authentication";
import { AxiosError } from "axios";
import { Dispatch } from "redux";
export interface ReturnedUser {
username: string;
}
export interface SignUpSuccessAction {
type: ActionTypes.SucceedSignUp;
payload: ReturnedUser;
}
export interface SignUpFailAction {
type: ActionTypes.FailSignUp;
payload: string;
}
export interface SignUpStartAction {
type: ActionTypes.StartSignUp;
}
const signUpStarted = (): SignUpStartAction => ({
type: ActionTypes.StartSignUp
});
const signUpSucceeded = (user: ReturnedUser): SignUpSuccessAction => ({
type: ActionTypes.SucceedSignUp,
payload: user
});
const signUpFailed = (error: string): SignUpFailAction => ({
type: ActionTypes.FailSignUp,
payload: error
});
export const signUpFetch = (user: User) => {
return async (dispatch: Dispatch) => {
dispatch(signUpStarted());
SignUpUser(user).then(
(response: any) => {
const { username } = response;
return dispatch(signUpSucceeded(username));
},
(error: AxiosError) => {
let errorMessage = "Internal Server Error";
if (error.response) {
errorMessage = error.response.data;
}
return dispatch(signUpFailed(errorMessage));
}
);
};
};
reducers/reducer.ts:
import { Action, ActionTypes } from "../actions";
export const SignUpReducer = (state = {}, action: Action) => {
switch (action.type) {
case ActionTypes.SucceedSignUp:
return { ...state, user: action.payload };
case ActionTypes.FailSignUp:
return { ...state, error: action.payload };
default:
return state;
}
};
reducers/index.ts:
import { SignUpReducer } from "./signUp";
import { combineReducers } from "redux";
export const reducer = combineReducers({
signUp: SignUpReducer
});
index.tsx:
import React from "react";
import ReactDOM from "react-dom";
import SignUp from "./containers/Signup/SignUp";
import { Provider } from "react-redux";
import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import { composeWithDevTools } from "redux-devtools-extension";
import { reducer } from "./reducers/index";
const store = createStore(reducer, composeWithDevTools(applyMiddleware(thunk)));
const App = () => <SignUp />;
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
SignUp.tsx:
import React, { useState } from "react";
import Button from "#material-ui/core/Button";
import { connect } from "react-redux";
import { Form, Field } from "react-final-form";
import { makeStyles, Theme, createStyles } from "#material-ui/core/styles";
import Grid from "#material-ui/core/Grid";
import CardWrapper from "../../components/CardWrapper";
import PasswordField from "../../components/Password";
import TextField from "../../components/TextField";
import { validate, submit } from "./validation";
const useStyles = makeStyles((theme: Theme) =>
createStyles({
container: {
padding: 16,
margin: "auto",
maxWidth: "100%",
flexGrow: 1
},
paper: {
padding: 16
},
item: {
marginTop: 16
}
})
);
const SignUp = () => {
const classes = useStyles();
const [showPassword, setPassword] = useState(false);
const handleClickShowPassword = () => {
setPassword(!showPassword);
};
const handleMouseDownPassword = (
event: React.MouseEvent<HTMLButtonElement>
) => {
event.preventDefault();
};
return (
<div className={classes.container}>
<Form
onSubmit={submit}
validate={validate}
render={({ handleSubmit, form, submitting, pristine }) => (
<form onSubmit={handleSubmit}>
<CardWrapper title='SignUp Form'>
<Grid container justify='center' spacing={3}>
<Grid item md={12}>
<Field fullWidth required name='username'>
{props => (
<TextField
label='Username'
type='text'
value={props.input.value}
onChange={props.input.onChange}
onBlur={props.input.onBlur}
meta={props.meta}
fullWidth={true}
/>
)}
</Field>
</Grid>
<Grid item md={12}>
<Field fullWidth required name='email'>
{props => (
<TextField
label='Email'
type='email'
value={props.input.value}
onChange={props.input.onChange}
onBlur={props.input.onBlur}
meta={props.meta}
fullWidth={true}
/>
)}
</Field>
</Grid>
<Grid item md={12}>
<Field fullWidth required name='password'>
{props => (
<PasswordField
value={props.input.value}
handleChange={props.input.onChange}
showPassword={showPassword}
handleClickShowPassword={handleClickShowPassword}
handleMouseDownPassword={handleMouseDownPassword}
fullWidth={true}
onBlur={props.input.onBlur}
meta={props.meta}
/>
)}
</Field>
</Grid>
<Grid item className={classes.item}>
<Button
type='button'
variant='contained'
onClick={form.reset}
disabled={submitting || pristine}
>
Reset
</Button>
</Grid>
<Grid item className={classes.item}>
<Button
variant='contained'
color='primary'
type='submit'
disabled={submitting || pristine}
>
Submit
</Button>
</Grid>
</Grid>
</CardWrapper>
</form>
)}
/>
</div>
);
};
export default connect()(SignUp);
validation.ts:
interface SignUpValues {
email: string;
password: string;
username: string;
}
const submit = (values: SignUpValues) => {
const user = {
username: values.username,
email: values.email,
password: values.password
};
return signUpFetch(user);
};
export { submit };
I find a similar question posted about the same issue described by Redux Dispatch Not Working in Action Creator but the answer does not fix my problem. Does I make something wrong when linking the different component with redux?
It wont dispatch because in component You didnt dispatch Your function
return signUpFetch(user)
Instead Connect the component with Redux and dispatch the function
in Index.tsx
import { connect } from 'react-redux';
const mapDispatchToProps = {
submit
};
export default connect(null, mapDispatchToProps)(SignUp);
And access it with
this.props.submit
Add dispatch in Submit function
const submit = (values: SignUpValues) =>(dispatch, getState)=> {
const user = {
username: values.username,
email: values.email,
password: values.password
};
return dispatch(signUpFetch(user));
};
Whenever you need to update redux state, dispatch the function from where it is also called and also in the actions.
You need to connection the component to the store when you do the dispatch:
import { connect } from 'react-redux';
const submit = (values: SignUpValues) => {
const user = {
username: values.username,
email: values.email,
password: values.password
};
return this.props.signUpFetch(user);
};
export const connectedSubmit = connect(null, {signUpFetch})(submit);
import { validate, connectedSubmit as submit } from "./validation";
And also you can just return at SignUpUser
export const signUpFetch = (user: User) => {
return async (dispatch: Dispatch) => {
dispatch(signUpStarted());
return SignUpUser(user).then(
(response: any) => {
const { username } = response;
dispatch(signUpSucceeded(username));
},
(error: AxiosError) => {
let errorMessage = "Internal Server Error";
if (error.response) {
errorMessage = error.response.data;
}
dispatch(signUpFailed(errorMessage));
}
);
};
}

Resources