I have this code which his basic todo list that I am trying to build in react
const Home = (props) => {
const classes = useStyles();
const [addNewTask, setAddNewTask] = useState(false);
const [formData, setFormData] = React.useState({ taskText: "" });
const dispatch = useDispatch();
useEffect(() => {
dispatch(getTasks());
}, [dispatch]);
const todos = useSelector((state) => state.todos);
const handleAddNew = () => {
setAddNewTask(addNewTask ? false : true);
};
const handleChange = (e) => {
setFormData({ ...formData, [e.target.name]: e.target.value });
};
const handleSubmit = (e) => {
e.preventDefault();
console.log(formData);
dispatch(createTask(formData));
dispatch(getTasks())
};
return (
<Grow in>
<Container>
<Grid
className={classes.adjustTop}
container
justify="space-between"
alignItems="stretch"
spacing={3}
>
<Grid item xs={2} sm={2}></Grid>
<Grid item xs={8} sm={8}>
<Typography variant="h2">TODO LIST</Typography>
{todos==null?
<Typography>Loading...</Typography>
:
<>
{todos?.tasks?.map((todo) => (
<Task id={todo.task} todo={todo} />
))}
</>
}
<div style={{ textAlign: "center" }}>
{addNewTask ? (
<>
<Button
variant="contained"
size="large"
color="error"
style={{ marginTop: "10px" }}
onClick={handleAddNew}
>
Cancel Adding task
</Button>
<form style={{ paddingTop: "20px" }} onSubmit={handleSubmit}>
<Input
name="taskText"
label="Task Text"
handleChange={handleChange}
type="text"
placeholder="text"
/>
<br />
<Button type="submit">Submit</Button>
</form>
</>
) : (
<Button
variant="contained"
size="large"
color="primary"
style={{ marginTop: "10px" }}
onClick={handleAddNew}
>
+ Add new task
</Button>
)}
</div>
</Grid>
<Grid item xs={2} sm={2}></Grid>
</Grid>
</Container>
</Grow>
);
};
export default Home;
This is the reducer
import { FETCH_ALL,CREATE } from '../constants/actionTypes';
export default (state = {tasks:null}, action) => {
switch (action.type) {
case FETCH_ALL:
return {...state,tasks:action.payload,errors:null}
case CREATE:
return {...state,tasks:action.payload,errors:null}
default:
return state;
}
};
& actions
import { FETCH_ALL,CREATE} from '../constants/actionTypes';
import * as api from '../api/index.js';
export const getTasks = () => async (dispatch) => {
try {
const { data } = await api.fetchTasks();
console.log(data);
dispatch({ type: FETCH_ALL, payload: data });
} catch (error) {
console.log(error.message);
}
};
export const createTask = (taskText) => async (dispatch) => {
try {
const { data } = await api.createTask(taskText);
dispatch({type:CREATE,payload:data})
} catch (error) {
console.log(error.message);
}
};
I am able to add the data to database on submit using handleSubmit tot the database but the issue is after each submit its giving me an error TypeError: _todos$tasks.map is not a function
I tried to handle this by using ternary operator & rendering Loading text on null & also have used chaining operator,but still getting same error
You are trying to get todos from your redux store
const todos = useSelector((state) => state.todos);
But there is no todos in your state and plus you are updating another state tasks in your reducer:
export default (state = {tasks:null}, action) => {
switch (action.type) {
case FETCH_ALL:
return {...state,tasks:action.payload,errors:null}
case CREATE:
return {...state,tasks:action.payload,errors:null}
default:
return state;
}
};
Related
I am working on something else but the below code fragment is similar to the problem I am facing. In the below code, the input field is getting generated dynamically.
I wanted to handle the input onChange event so I took a state variable like normally we do and used that to handle my input state and of course, it isn't working, if I change one input field the others are also getting affected which now seem obvious why it's happening.
import { useState } from "react";
export default function App() {
let [arr, setArr] = useState(["a", "b", "c", "d"]);
const [input, setInput] = useState(0);
return (
<div className="App">
{arr.map((item, idx) => {
return (
<div key={idx}>
<p>{item}</p>
<input
placeholder="enter value"
type="number"
value={input}
onChange={(e) => setInput(e.target.value)}
/>
</div>
);
})}
</div>
);
}
Edit: This is the original problem.
Stocks.jsx
I have a quantity state variable that I am using to manage the state of my input element. But, when there will be more than one input element then it won't work and the issue it creates is that a change in any input field also changes the other input fields.
import {
Box,
Button,
Grid,
GridItem,
Heading,
Image,
Input,
Text,
} from "#chakra-ui/react";
import React, { useEffect, useState } from "react";
import { useSelector } from "react-redux";
import { getStock } from "../redux/appReducer/action";
import { useDispatch } from "react-redux";
export const Stocks = () => {
const dispatch = useDispatch();
const stocks = useSelector((store) => store.AppReducer.stocks);
/*
Format of the data coming in the stocks
[
{
"id" : 1,
"company_logo": "https://logolook.net/wp-content/uploads/2021/11/HDFC-Bank-Logo.png",
"company_name": "HDFC Bank",
"company_type": "Bank",
"stock_exchange": "NSE",
"total_shares": 50000,
"cost_per_share": 4210,
"price_action": 12,
},
{
"id" : 2,
"company_logo": "https://www.jetspot.in/wp-content/uploads/2018/03/reliance-logo.jpg",
"company_name": "Reliance Industries",
"company_type": "Oil",
"stock_exchange": "BSE",
"total_shares": 20000,
"cost_per_share": 2132,
"price_action": 4,
}
]
*/
const [quantity, setQuantity] = useState(0);
useEffect(() => {
dispatch(getStock());
}, []);
return (
<Grid
templateColumns="repeat(2, 350px)"
templateRows="repeat(2, auto)"
justifyContent="center"
alignItems="center"
gap="20px"
pt="30px"
pb="30px"
>
{stocks?.map((stock, index) => {
return (
<GridItem
boxShadow="rgba(3, 102, 214, 0.3) 0px 0px 0px 3px"
justifyContent="center"
p="20px"
key={stock.id}
>
<Box mb="10px">
<Image h="80px" src={stock?.company_logo} />
</Box>
<Box mb="10px">
<Heading size="md">{stock?.company_name}</Heading>
</Box>
<Box mb="10px">
<Text as="span">EXCHANGE: </Text>
<Text as="span">{(stock?.stock_exchange).toUpperCase()}</Text>
</Box>
<Box mb="10px">
<Text as="span">TYPE: </Text>
<Text as="span">{(stock?.company_type).toUpperCase()}</Text>
</Box>
<Box mb="10px">
<Text as="span">TOTAL SHARES: </Text>
<Text as="span">{stock?.total_shares}</Text>
</Box>
<Box mb="10px">
<Text as="span">COST PER SHARE: </Text>
<Text as="span">{stock?.cost_per_share}</Text>
</Box>
<Box mb="10px">
<Text as="span">PRICE ACTION: </Text>
<Text as="span">{stock?.price_action}</Text>
</Box>
<Box mb="10px">
<Input
value={quantity}
type="number"
placeholder="Quantity"
onChange={(e) => setQuantity(e.target.value)}
/>
</Box>
<Box mb="10px">
<Button colorScheme="green">Buy</Button>
</Box>
</GridItem>
);
})}
</Grid>
);
};
action.js
import * as types from "./actionTypes";
import axios from "axios";
export const getStock = () => (dispatch) => {
dispatch({ type: types.GET_STOCK_REQUEST });
return axios
.get("https://stockbroker.onrender.com/companies")
.then((res) => {
// console.log(res);
dispatch({ type: types.GET_STOCK_SUCCESS, payload: res.data });
})
.catch((e) => dispatch({ type: types.GET_STOCK_FAILURE }));
};
reducer.js
import * as types from "./actionTypes";
const initialState = {
isLoading: false,
isError: false,
stocks: [],
};
export const reducer = (state = initialState, { type, payload }) => {
switch (type) {
case types.GET_STOCK_REQUEST:
return {
...state,
isLoading: true,
};
case types.GET_STOCK_SUCCESS:
return {
...state,
isLoading: false,
stocks: payload,
};
case types.GET_STOCK_FAILURE:
return {
...state,
isLoading: false,
isError: true,
stocks: [],
};
default:
return state;
}
};
if turning arr into an object is an option, you could do just that, and use it as the control state for the inputs:
import React from 'react';
import { useState } from "react";
export function App() {
const [obj, setObj] = useState({
a: 0,
b: 0,
c: 0,
d: 0
});
return (
<div className="App">
{Object.keys(obj).map((item, idx) => {
return (
<div key={idx}>
<p>{item}</p>
<input
placeholder="enter value"
type="number"
value={obj[item]}
onChange={(e) => setObj(prev => ({...prev, [item]: e.target.value}))}
/>
</div>
);
})}
</div>
);
}
//StudentsFilter.jsx
import {
Accordion,
AccordionButton,
AccordionIcon,
AccordionItem,
AccordionPanel,
Badge,
Box,
Button,
Checkbox,
Flex,
Radio,
RadioGroup,
Text,
useColorMode,
useColorModeValue,
VStack,
} from "#chakra-ui/react";
import React, { useState } from "react";
import { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { axiosInstance } from "../../../axiosConfig";
import { commonSlidercss, darkSlidercss } from "../../../GlobalStyles";
import {
setFilterSearchMode,
setSearchingBatch,
setSearchingStream,
} from "../../../redux/slices/adminUserSlice";
const StudentsFilter = () => {
const [streams, setStreams] = useState(null);
const [batchYear, setBatchYear] = useState([]);
const [checkedStreams, setCheckedStreams] = useState([]);
const [checkedBatches, setCheckedBatches] = useState([]);
const siteState = useSelector((state) => state.siteReducer);
const adminUsers = useSelector((state) => state.adminUserReducer);
const filterSearchMode = adminUsers?.filterSearchMode;
const site = siteState.siteInfo;
const { colorMode } = useColorMode();
const dispatch = useDispatch();
useEffect(() => {
getStream();
let batches = [];
if (site) {
let year = site.year_established;
let current_year = new Date().getFullYear();
let year_diff = current_year - site.year_established;
for (let i = 0; i <= year_diff; i++) {
batches.push(year + i);
}
setBatchYear(batches);
}
}, [site]);
const getStream = async () => {
try {
const res = await axiosInstance.get("stream");
setStreams(res?.data?.stream);
} catch (error) {
console.log("Something went wrong while getting streams", e);
}
};
const streamsHandler = (e, li) => {
e.stopPropagation();
const index = checkedStreams.indexOf(li);
if (index > -1) {
setCheckedStreams([
...checkedStreams.slice(0, index),
...checkedStreams.slice(index + 1),
]);
} else {
setCheckedStreams([...checkedStreams, li]);
}
};
const batchesHandler = (e, li) => {
e.stopPropagation();
const index = checkedBatches.indexOf(li);
if (index > -1) {
setCheckedBatches([
...checkedBatches.slice(0, index),
...checkedBatches.slice(index + 1),
]);
} else {
setCheckedBatches([...checkedBatches, li]);
}
};
useEffect(() => {
dispatch(setSearchingStream(checkedStreams));
dispatch(setSearchingBatch(checkedBatches));
}, [checkedBatches, checkedStreams]);
return (
<Flex
p="6"
direction="column"
style={{ height: "inherit" }}
align="space-between"
justify="space-between"
w="300px"
maxH={231}
overflowY="scroll"
css={colorMode === "light" ? commonSlidercss : darkSlidercss}
>
<Box>
<Text fontWeight="medium" fontSize="sm" mb={7}>
More filters
</Text>
<Accordion allowMultiple>
<AccordionItem>
<AccordionButton>
<Box flex="1" fontSize="xs" textAlign="left">
Batch
</Box>
<AccordionIcon />
</AccordionButton>
<AccordionPanel pb={4}>
<RadioGroup>
<VStack align="start">
{batchYear &&
batchYear.map((li) => (
<Checkbox
// onChange={checkboxChange}
key={li}
value={li}
colorScheme={useColorModeValue(
"primaryScheme",
"purple"
)}
size="sm"
onChange={(e) => batchesHandler(e, li)}
isChecked={checkedBatches.includes(li)}
>
<Text fontSize="xs">{li}</Text>
</Checkbox>
))}
</VStack>
</RadioGroup>
</AccordionPanel>
</AccordionItem>
<AccordionItem>
<AccordionButton>
<Box flex="1" textAlign="left" fontSize="xs">
Stream
</Box>
<AccordionIcon />
</AccordionButton>
<AccordionPanel pb={4}>
<RadioGroup>
<VStack align="start">
{streams &&
streams.map((li) => (
<Checkbox
// onChange={checkboxChange}
key={li.id}
value={li.id}
colorScheme={useColorModeValue(
"primaryScheme",
"purple"
)}
size="sm"
onChange={(e) => streamsHandler(e, li.id)}
isChecked={checkedStreams.includes(li.id)}
>
<Text fontSize="xs">{li?.name}</Text>
</Checkbox>
))}
</VStack>
</RadioGroup>
</AccordionPanel>
</AccordionItem>
</Accordion>
</Box>
<Box>
<Button
width="full"
h="40px"
borderRadius="10px"
fontWeight="500"
variant="primary"
mt="10px"
onClick={() => dispatch(setFilterSearchMode(!filterSearchMode))}
>
Filter
</Button>
</Box>
</Flex>
);
};
export default StudentsFilter;
What is the reason why I am getting the error " React has detected a change in the order of Hooks called by StudentsFilter. This will lead to bugs and errors if not fixed" I have seen this warning in 2-3 components and also tried to correct it but I don't know what I am doing wrong? Can someone help me to identify it?
You're calling the useColorModeValue conditionally (and in a loop) in the return statement. That's probably the source of the error.
You should use ESLint and the "rules of hooks" rule, it would have been highlighted directly in your editor.
Now I have created this custom hook to perform lazy loading,which takes redux slice action as input and
import { useState, useEffect, useCallback, useRef } from "react";
import { useDispatch } from "react-redux";
function useLazyFetch(fetchAction) {
const dispatch = useDispatch();
const [page, setPage] = useState(1);
const [loading, setLoading] = useState(false);
const loadMoreRef = useRef(null);
const handleObserver = useCallback(async(entries) => {
const [target] = entries;
console.log(target.isIntersecting);
if (target.isIntersecting) {
console.log("INTERSECTING.....");
await new Promise((r) => setTimeout(r, 2000));
setPage((prev) => prev + 1);
}
}, []);
useEffect(() => {
const option = {
root: null,
rootMargin: "0px",
threshold: 1.0,
};
const observer = new IntersectionObserver(handleObserver, option);
if (loadMoreRef.current) observer.observe(loadMoreRef.current);
}, [handleObserver]);
const fetchApi = useCallback(async () => {
try {
setLoading(true);
await new Promise((r) => setTimeout(r, 2000));
dispatch(fetchAction(page))
setLoading(false);
} catch (err) {
console.error(err);
}
}, [page,fetchAction,dispatch]);
useEffect(() => {
fetchApi();
}, [fetchApi]);
return { loading, loadMoreRef };
}
export default useLazyFetch;
I am using this in my component like this, here you can see I am tracking div in the bottom using loadMoreRef from useLazyFetch, Now when I am commenting out the fetchApi(); from custom hook its working as expected, on scroll its logging INTERSECTING... in the console but the moment I try to execute the action through fetchApi() my whole app goes into loop,the div tracker with ref comes to top and it fetches the posts but after immediately that action repeats the tracker comes to top and page becomes empty & it fetches next set of posts,I can see that my list is getting appended new set of posts to state in redux dev tool instead of completely setting new state, but in UI it's rendering all posts again and again whic is causing the loop,how can I avoid this ?
import { CircularProgress, Grid, IconButton, Typography } from "#mui/material";
import { Box } from "#mui/system";
import React, { useEffect,useRef,useState } from "react";
import AssistantIcon from "#mui/icons-material/Assistant";
import Post from "../components/Post";
import { useDispatch, useSelector } from "react-redux";
import { getPosts } from "../redux/postSlice";
import AddPost from "../components/AddPost";
import useLazyFetch from "../hooks/useLazyFetch";
export default function Home() {
const dispatch = useDispatch();
// const api = `https://picsum.photos/v2/list`
const { status, posts } = useSelector((state) => state.post);
const {loading,loadMoreRef} = useLazyFetch(getPosts)
useEffect(() => {
dispatch(getPosts());
}, []);
return (
<Box>
<Box borderBottom="1px solid #ccc" padding="8px 20px">
<Grid container justifyContent="space-between" alignItems="center">
<Grid item>
<Typography variant="h6">Home</Typography>
</Grid>
<Grid item>
<IconButton>
<AssistantIcon />
</IconButton>
</Grid>
</Grid>
</Box>
<Box height="92vh" sx={{ overflowY: "scroll" }}>
<AddPost />
<Box textAlign="center" marginTop="1rem">
{status === "loading" && (
<CircularProgress size={20} color="primary" />
)}
</Box>
{status === "success" &&
posts?.map((post) => <Post key={post._id} post={post} />)}
<div style={{height:"50px",width:"100px",backgroundColor:"red"}} ref={loadMoreRef}>{loading && <p>loading...</p>}</div>
</Box>
</Box>
);
}
And here is my redux action & state update part
const initialState = {
status: "idle",
posts: []
};
export const getPosts = createAsyncThunk("post/getPosts", async (page) => {
console.log(page);
console.log("calling api ...");
const { data } = await axios.get(`/api/posts?page=${page}`);
return data;
});
export const postSlice = createSlice({
name: "post",
initialState,
reducers: {},
extraReducers: {
[getPosts.pending]: (state, action) => {
state.status = "loading";
},
[getPosts.fulfilled]: (state, action) => {
state.status = "success";
state.posts = [...state.posts,...action.payload.response.posts] ;
},
[getPosts.rejected]: (state, action) => {
state.status = "failed";
},
}
this is the solution that is working
import { CircularProgress, Grid, IconButton, Typography } from "#mui/material";
import { Box } from "#mui/system";
import React, { useEffect,useMemo } from "react";
import AssistantIcon from "#mui/icons-material/Assistant";
import Post from "../components/Post";
import { useDispatch, useSelector } from "react-redux";
import { getPosts } from "../redux/postSlice";
import AddPost from "../components/AddPost";
import useLazyFetch from "../hooks/useLazyFetch";
export default function Home() {
const { status, posts } = useSelector((state) => state.post);
const {loading,loadMoreRef} = useLazyFetch(getPosts)
const renderedPostList = useMemo(() => (
posts.map((post) => {
return( <Post key={post._id.toString()} post={post} />)
})
), [posts])
return (
<Box>
<Box borderBottom="1px solid #ccc" padding="8px 20px">
<Grid container justifyContent="space-between" alignItems="center">
<Grid item>
<Typography variant="h6">Home</Typography>
</Grid>
<Grid item>
<IconButton>
<AssistantIcon />
</IconButton>
</Grid>
</Grid>
</Box>
<Box height="92vh" sx={{ overflowY: "scroll" }}>
<AddPost />
<Box textAlign="center" marginTop="1rem">
{status === "loading" && (
<CircularProgress size={20} color="primary" />
)}
</Box>
{renderedPostList}
<div style={{height:"50px",width:"100px",backgroundColor:"red"}} ref={loadMoreRef}>{loading && <p>loading...</p>}</div>
</Box>
</Box>
);
}
}
I used useMemo hook to memoize and it works as expected
I'm working on a simple React app alongside TypeScript, and I'm using JSONPlaceholder for API calls simulation. I have implemented everything I need, but facing a problem when it comes to re-rendering components that shows response data from API. The thing is that if I do HTTP GET to all data after I do DELETE or PUT, I will again get all same data because data changes don't actually apply on the server.
I will appreciate it if you can help me how to edit my functions for DELETE and PUT.
When I console.log my responses inside components, I get staus ok 200, so the API call part is fine, the only thing that I struggle with is how to re-render components properly
There is a component that shows all data, and also make calls to DELETE endpoint, and shows modal where I call PUT endpoint:
const PostsTable: React.FunctionComponent = () => {
const [posts, setPosts] = useState<Array<IPost>>([]);
const [selectedPost, setSelectedPost] = useState<IPost>({});
const [dialogOpen, setDialogOpen] = useState<boolean>(false);
const fetchPosts = () => {
PostService.getAllPosts()
.then((response: any) => {
setPosts(response.data);
})
.catch((e: Error) => {
console.log(e);
});
};
useEffect(() => {
fetchPosts();
}, []);
const editPost = (post: IPost) => (event: any) => {
setSelectedPost(post);
setDialogOpen(true);
};
const handleClose = () => {
setDialogOpen(false);
};
const deletePost =
(id: any): any =>
(event: Event) => {
event.stopPropagation();
PostService.deletePost(id)
.then((response: any) => {
setPosts(posts);
console.log(response);
})
.catch((e: Error) => {
console.log(e);
});
};
return (
<Container fixed>
{!posts || posts.length < 1 ? (
<div style={{ display: 'flex', justifyContent: 'center' }}>
<CircularProgress color="primary" size={100} />
</div>
) : (
<TableContainer component={Paper}>
<Table sx={{ minWidth: 650 }} aria-label="simple table">
<TableHead>
<TableRow>
<TableCell align="left">User Id</TableCell>
<TableCell align="left">Post Id</TableCell>
<TableCell align="left">Title</TableCell>
<TableCell align="left">Actions</TableCell>
</TableRow>
</TableHead>
<TableBody>
{posts.map((post: IPost) => (
<TableRow
key={post.id}
sx={{ '&:last-child td, &:last-child th': { border: 0 } }}
>
<TableCell>{post.userId}</TableCell>
<TableCell align="left">{post.id}</TableCell>
<TableCell align="left">{post.title}</TableCell>
<TableCell align="left">
<Tooltip title="Delete">
<IconButton onClick={editPost(post)}>
<EditIcon />
</IconButton>
</Tooltip>
<Tooltip title="Edit">
<IconButton onClick={deletePost(post.id)}>
<DeleteIcon />
</IconButton>
</Tooltip>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
)}
{
<EditPostDialog
open={dialogOpen}
handleClose={handleClose}
selectedPost={selectedPost}
/>
}
</Container>
);
};
export default PostsTable;
And also, there is a modal component with PUT function:
const EditPostDialog: React.FunctionComponent<IEditPostDialog> = (
props: IEditPostDialog
) => {
const { open, handleClose, selectedPost } = props;
const [post, setPost] = useState<IPost>({});
useEffect(() => {
const newPost = {
id: selectedPost.id,
userId: selectedPost.userId,
title: selectedPost.title,
body: selectedPost.body,
};
setPost(newPost);
}, [selectedPost]);
const handleChange = (event: any) => {
setPost({ ...post, [event.target.name]: event.target.value });
};
const handleSubmit = () => {
PostService.updatePost(post.id, post)
.then((response: any) => {
console.log(response);
})
.catch((e: Error) => {
console.log(e);
});
handleClose();
};
return (
<Dialog onClose={handleClose} open={open}>
<DialogTitle id="simple-dialog-title">Post info</DialogTitle>
<DialogContent classes={{ root: 'dialog-content' }}>
<TextField
id="userId"
label="User Id"
name="userId"
variant="outlined"
value={post.userId}
onChange={handleChange}
style={{ marginTop: 16 }}
/>
<TextField
id="title"
label="Title"
name="title"
variant="outlined"
value={post.title}
onChange={handleChange}
/>
<TextField
id="body"
label="Body"
name="body"
variant="outlined"
value={post.body}
onChange={handleChange}
multiline
/>
</DialogContent>
<DialogActions>
<Button onClick={handleClose}>Close</Button>
<Button onClick={handleSubmit}>Submit</Button>
</DialogActions>
</Dialog>
);
};
export default EditPostDialog;
And at the end, my PostService.ts and HTTP files where is axios code to call API points:
const getAllPosts = () => {
return http.get<Array<IPost>>('/posts');
};
const getSinglePost = (id: number | undefined) => {
return http.get<IPost>(`/posts/${id}`);
};
const createPost = (data: IPost) => {
return http.post<IPost>('/posts', data);
};
const updatePost = (id: number | undefined, data: IPost) => {
return http.put<any>(`/posts/${id}`, data);
};
const deletePost = (id: number | undefined) => {
return http.delete<any>(`/posts/${id}`);
};
const PostService = {
getAllPosts,
getSinglePost,
createPost,
updatePost,
deletePost,
};
export default axios.create({
baseURL: 'https://jsonplaceholder.typicode.com',
headers: {
'Content-type': 'application/json',
},
});
Thank you all guys :)
Your delete function uses "setPosts(posts)" which I'm pretty sure is not what you meant. Also, your modal does not tell your main component that a post has been edited, I guess that's the problem ? Pass a callback to the modal to update the posts state in the main component.
I've got a component, that displays a project. For each project, there is a delete button, but for some reason, the delete buttons of all my projects get "pressed" immediately.
Why would this happen? I'm using redux and firestore. I think this has maybe something to do with the realtime listener, but don't have any problems with adding data to the database.
Projects.js
componentDidMount = () => {
this.props.projectActions.registerProjectListener();
};
renderProjects = () => {
const { projects } = this.props.projects;
const { classes, projectActions } = this.props;
return (
<Paper elevation={0} square={true} className={classes.projectPaper}>
<Box fontSize="h5.fontSize" textAlign="center">
Your Projects:
</Box>
{projects &&
projects.map(project => {
return <Project {...{ key: project.id, project, projectActions }}></Project>;
})}
<Grid container className={classes.grid} direction="row" justify="flex-end" alignItems="center">
<AddProject {...{ projectActions }}></AddProject>
</Grid>
</Paper>
);
};
Project.js
export class Project extends Component {
render() {
const { classes, project, projectActions } = this.props;
console.log(project.id);
return (
<Paper elevation={3} variant="outlined" className={classes.paper}>
<Grid container justify="space-between">
<Box fontSize="h6.fontSize">{project.name}</Box>
<IconButton onClick={projectActions.deleteProject(project.id)}> //This gets fired immediately for every project
<ClearIcon></ClearIcon>
</IconButton>
</Grid>
</Paper>
);
}
}
actions
export const deleteProject = id => {
return dispatch => {
console.log(db.collection("projects").doc(id));
// db.collection("projects")
// .doc(id)
// .delete();
dispatch({ type: ActionTypes.DELETE_PROJECT });
};
};
export const registerProjectListener = () => {
let projects = [];
return dispatch => {
db.collection("projects").onSnapshot(snapshot => {
snapshot.docChanges().forEach(change => {
if (change.type === "added") {
console.log(change.doc.data());
projects.push({ ...change.doc.data(), ...{ id: change.doc.id } });
} else if (change.type === "removed") {
// projects.filter(()=> )
}
});
dispatch({ type: ActionTypes.REGISTER_LISTENER, projects: projects });
});
};
};
You need to pass reference to a function. Update the IconButton component with the following.
<IconButton
onClick={() => projectActions.deleteProject(project.id)}>
<ClearIcon></ClearIcon>
</IconButton>