making delete request to mongodb database and getting an error message action must be plain object. but if i refresh the store object is deleted. but i want do it live. i have implmented get edit and create request using the same method but when using delete its not happening. what i am doing wrong.import React from "react";
error message : redux.js:278 Uncaught Error: Actions must be plain objects. Instead, the actual type was: 'Promise'. You may need to add middleware to your store setup to handle dispatching other values, such as 'redux-thunk' to handle dispatching functions.
i am dipatching the action from post.js button component as onClick={() => dispatch(deletePost(post._id))}
const Post = ({ post, setCurrentId }) => {
const classes = useStyles();
const dispatch = useDispatch();
const editHandeler = () => {
setCurrentId(post._id);
};
return (
<Card className={classes.card}>
<CardMedia
className={classes.media}
image={
post.selectedFile ||
"https://user-images.githubusercontent.com/194400/49531010-48dad180-f8b1-
11e8-8d89-1e61320e1d82.png"
}
title={post.title}
/>
<div className={classes.overlay}>
<Typography variant="h6">{post.creator}</Typography>
<Typography variant="body2">
{moment(post.createdAt).fromNow()}
</Typography>
</div>
<div className={classes.overlay2}>
<Button style={{ color: "white" }} size="small" onClick={editHandeler}>
<MoreHorizIcon fontSize="default" />
</Button>
</div>
<div className={classes.details}>
<Typography variant="body2" color="textSecondary" component="h2">
{post.tags.map((tag) => `#${tag} `)}
</Typography>
</div>
<Typography
className={classes.title}
gutterBottom
variant="h5"
component="h2"
>
{post.title}
</Typography>
<CardContent>
<Typography variant="body2" color="textSecondary" component="p">
{post.message}
</Typography>
</CardContent>
<CardActions className={classes.cardActions}>
<Button size="small" color="primary" onClick={() => {}}>
<ThumbUpAltIcon fontSize="small" /> Like {post.likeCount}{" "}
</Button>
<Button
size="small"
color="primary"
onClick={() => dispatch(deletePost(post._id))}
>
<DeleteIcon fontSize="small" /> Delete
</Button>
</CardActions>
</Card>
);
};
export default Post;
delete post function
export const deletePost = (id) => async (dispatch) => {
try {
await api.deletePost(id);
dispatch({ type: DELETE, payload: id });
} catch (error) {
console.log(error.message);
}
};
api
const url = "http://localhost:5000/posts";
export const fetchPosts = () => axios.get(url);
export const createPost = (newPost) => axios.post(url, newPost);
export const updatePost = (id, updatedPost) =>
axios.patch(`${url}/${id}`, updatedPost);
export const deletePost = (id) => axios.delete(`${url}/${id}`);
reducer
import { FETCH_ALL, CREATE, UPDATE, DELETE, LIKE } from "../actions/constants";
const reducer = (posts = [], action) => {
switch (action.type) {
case FETCH_ALL:
return action.payload;
case CREATE:
return [...posts, action.payload];
case UPDATE:
return posts.map((post) =>
post._id === action.payload._id ? action.payload : post
);
case DELETE:
return posts.filter((post) => post.id !== action.payload);
default:
return posts;
}
};
export default reducer;
store
import { configureStore } from "#reduxjs/toolkit";
import { reducers } from "./rootreducer";
import { applyMiddleware, compose } from "redux";
import thunk from "redux-thunk";
const store = configureStore(
{ reducer: reducers },
compose(applyMiddleware(thunk))
);
export default store;
rootreducer
export const reducers = combineReducers({
posts: posts,
});
Related
I am trying to fetch the local json data and then using it inside my component.
I have mentioned the extraReducers while keeping the reducer empty. Below is the code,
import { createAsyncThunk, configureStore } from "#reduxjs/toolkit";
export const fetchData = createAsyncThunk('data/fetch', async () =>{
const response = await fetch('../src/assets/relatives.json');
const data = await response.json();
return data;
})
const dataSlice = {
name: 'data',
initialState: [],
reducers: {},
extraReducers: (builder) => {
builder
.addCase(fetchData.pending, (state) => {
state.status = 'loading';
})
.addCase(fetchData.fulfilled, (state, action) => {
state.status = 'succeeded';
state.data = action.payload;
})
.addCase(fetchData.rejected, (state, action) => {
state.status = 'failed';
state.error = action.error.message;
});
},
};
const store = configureStore({
reducer:{
data: dataSlice,
},
preloadedState: {data:[]},
})
export default store;
This is my component:
import React, { useEffect } from "react";
import Card from "#mui/material/Card";
import CardActions from "#mui/material/CardActions";
import CardContent from "#mui/material/CardContent";
import Button from "#mui/material/Button";
import Typography from "#mui/material/Typography";
import TreeView from "#mui/lab/TreeView";
import { ExpandMore ,ChevronRight } from "#mui/icons-material";
import TreeItem from "#mui/lab/TreeItem";
import { useDispatch, useSelector } from "react-redux";
import { fetchData } from "../configureStore";
const FamilyFolder = () => {
const dispatch = useDispatch();
const data = useSelector((state = state.data.data));
const status = useSelector((state) => state.data.status);
useEffect(() => {
dispatch(fetchData());
}, [dispatch]);
if (status === "loading") {
return<div>Loading...</div>
}
if (status === 'failed') {
return <div>Error</div>;
}
console.log(data);
return (
<div>
<Card sx={{ minWidth: 50 }}>
<CardContent>
<Typography
sx={{ fontSize: 14, align: "center" }}
color="text.secondary"
gutterBottom
>
Family Tree
</Typography>
</CardContent>
<TreeView
aria-label="file system navigator"
defaultCollapseIcon={<ExpandMore />}
defaultExpandIcon={<ChevronRight />}
sx={{ height: 240, flexGrow: 1, maxWidth: 400, overflowY: "auto" }}
>
<TreeItem nodeId="1" label="gg">
<TreeItem nodeId="2" label="Calendar" />
</TreeItem>
<TreeItem nodeId="5" label="Documents">
<TreeItem nodeId="10" label="OSS" />
<TreeItem nodeId="6" label="MUI">
<TreeItem nodeId="8" label="index.js" />
</TreeItem>
</TreeItem>
</TreeView>
<CardActions className="parent-button-container">
<CardActions className="">
<Button variant="contained">Import JSON</Button>
<Button variant="contained">Add Family</Button>
</CardActions>
<CardActions className="">
<Button variant="contained">Export JSON</Button>
<Button variant="contained">Print Tree</Button>
</CardActions>
</CardActions>
</Card>
</div>
);
};
export default FamilyFolder;
I have added the preloadedState inside the store as initial state but that is also not working.
Should i mention anything inside reducer: {} ?. If yes, please tell what i should do
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 have created an empty object variable named workspacesData through the context api which is available globally across the app. I am trying to set my api data to the variable within the helper function that is below (getWorkspacesData).
This function getWorkspacesData is then being called on another page of the application for when the user wants to pull in the data and have it displayed on the page. Problem is when the user presses the button I get the error "Invalid hook call. Hooks can only be called inside of the body of a function component."
I know the issue is that I am using the useContext hook within this function, but how do I get around this issue? as I want to use the globally available variable to set the data to it.
getWorkspaces.js
import { useContext } from "react";
import { AppContext } from "../../../context/context";
import mavenlinkAPI from "../apiTools";
const GetWorkspaces = async (mavenlinkAccessToken) => {
const {setWorkspacesData} = useContext(AppContext);
try{
const data = await mavenlinkAPI(
'get',
'workspaces?token='+mavenlinkAccessToken,
);
console.log(data)
setWorkspacesData(data);
} catch (error) {
console.log(error)
}
}
export default GetWorkspaces;
MavenlinkPage.jsx
import { Container, Grid, Paper } from '#mui/material';
import React, {useContext, useEffect, useRef, useState} from 'react';
import { getAuth } from "firebase/auth";
import Fab from '#mui/material/Fab';
import AddIcon from '#mui/icons-material/Add';
import {SuccessSnackbar, ErrorSnackbar} from '../components/PopupSnackbar';
import { AppContext } from '../context/context';
import GetWorkspaces from '../helpers/api/mavenlink/getWorkspaces';
import GetTimesheets from '../helpers/api/mavenlink/getTimesheets';
import GetUsers from '../helpers/api/mavenlink/getUsers';
import CreateProject from '../helpers/api/mavenlink/createProject';
import GetMavenlinkAccessToken from '../helpers/api/mavenlink/getMavenlinkAccessToken';
import DummyDataHolder from '../components/dummyDataHolder';
// import { DataGrid } from '#material-ui/x-data-grid';
export const MavenlinkPage = () => {
const { mavenlinkConnected } = useContext(AppContext);
const [errorAlert, setErrorAlert] = useState(false);
const [successAlert, setSuccessAlert] = useState(false);
const { mavenlinkAccessToken, setMavenlinkAccessToken } = useContext(AppContext);
const { workspacesData } = useContext(AppContext);
const auth = getAuth();
const user = auth.currentUser;
const uid = user.uid
const handleAlertClose = (event, reason) => {
if (reason === 'clickaway') {
return;
}
setSuccessAlert(false) && setErrorAlert(false);
};
useEffect(() => {
if(mavenlinkConnected){
GetMavenlinkAccessToken(setMavenlinkAccessToken);
}
}, [])
console.log(mavenlinkAccessToken)
return(
<>
<Container>
<div className="mavenlink-page">
<Grid container spacing={2}>
<Grid item xs={12}>
<h1>Mavenlink</h1>
</Grid>
<Grid item xs={12}>
<Paper className="connection-status" elevation={1}>
<h4 className="title">Connection Status:</h4>
{!mavenlinkConnected ? <h4 className="response-error">{user.email} is not connected</h4> : <h4 className="response-success">{user.email} is connected</h4>}
</Paper>
</Grid>
<Grid item xs={12}>
<Paper elevation={1}>
<h4>Get Workspaces</h4>
<Fab onClick={() => GetWorkspaces(mavenlinkAccessToken)} color="primary" aria-label="add">
<AddIcon />
</Fab>
<h4>Get timesheets</h4>
<Fab onClick={() => GetTimesheets(mavenlinkAccessToken)} color="primary" aria-label="add">
<AddIcon />
</Fab>
<h4>Get users</h4>
<Fab onClick={() => GetUsers(mavenlinkAccessToken)} color="primary" aria-label="add">
<AddIcon />
</Fab>
<h4>Create project</h4>
<Fab onClick={() => CreateProject(mavenlinkAccessToken)} color="primary" aria-label="add">
<AddIcon />
</Fab>
</Paper>
</Grid>
<Grid item xs={12}>
<Paper elevation={10} style={{ height: 400, width: '100%' }}>
{workspacesData != null ?
Object.keys(workspacesData).map((item, index) => {
return(
<div key={index}>
{workspacesData[item].map((m, ind) =>
<div key={ind}>{item}</div>
)}
</div>
)
})
:
<div></div>
}
</Paper>
</Grid>
</Grid>
</div>
{successAlert === true ? <SuccessSnackbar open={successAlert} handleClose={handleAlertClose}/> : <></> }
{errorAlert === true ? <ErrorSnackbar open={errorAlert} handleClose={handleAlertClose}/> : <></> }
</Container>
</>
);
};
You can call the hook inside your component and pass the returned value from it to the function:
const getWorkspaces = async (mavenlinkAccessToken, onSuccess) => {
try{
const data = await mavenlinkAPI(
'get',
'workspaces?token='+mavenlinkAccessToken,
);
onSuccess?.(data)
} catch (error) {
console.log(error)
}
}
And call it as:
export const MavenlinkPage = () => {
const { setWorkspacesData } = useContext(AppContext);
...
onClick={() => getWorkspaces(mavenlinkAccessToken, setWorkspacesData )}
}
Or create a custom hook:
const useGetWorkspaces = (mavenlinkAccessToken) => {
const { setWorkspacesData } = useContext(AppContext);
return async () => {
try{
const data = await mavenlinkAPI(
'get',
'workspaces?token='+mavenlinkAccessToken,
);
setWorkspacesData(data);
} catch (error) {
console.log(error)
}
}
}
And use it like:
export const MavenlinkPage = () => {
const getWorkspaces = useGetWorkspaces(mavenlinkAccessToken);
...
onClick={() => getWorkspaces()}
}
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;
}
};
I'm receiving a cannot read props of undefined. I'm trying to destructure props but I need the hook calls. Is there a way for me to destructure props in a function or another way to resolve this issue?
ProductBrowse.jsx is formatting the products:
const ProductBrowse = () => {
const { id, name, img, store, price, desc, inCart } = this.props.product;
const [open, setOpen] = React.useState(false);
const openModal = () => {
setOpen(!open);
};
const closeModal = () => {
setOpen(!open);
};
return (
<Box border={1} borderRadius={3}>
<Card>
<CardActionArea>
<ProductModal
open={open}
onClick={() => openModal()}
onClose={() => closeModal()}
onSave={() => closeModal()}
productName={name}
productDesc={desc}
/>
<CardHeader
title={name}
subheader={formatCurrency(price)}
/>
<CardMedia
image={img}
alt={desc}
/>
<CardContent>
<Typography variant='body2' color='textSecondary' component='p'>
{desc}
</Typography>
</CardContent>
</CardActionArea>
<CardActions>
<Button size='small' /*To Checkout*/>BUY NOW</Button>
<Button
size='small'
onClick={() => {
console.log('Added to Cart');
}}
>
ADD TO CART
</Button>
<Button size='small'>REVIEW</Button>
</CardActions>
</Card>
</Box>
);
}
You can convert your class based component to a functional component like this:
const ProductBrowse = ({ product }) => {
const { id, name, img, store, price, desc, inCart } = product;
...
}
export default ProductBrowse;
As you can see, the product props are being destructured. The entire props object is available if you were to provide more props and want to use them as well.
i.e.
const ProductBrowse = (props) => {
const { id, name, img, store, price, desc, inCart } = props.product;
...
}
export default ProductBrowse;
You are trying to use hooks in class based components. Please refer converted functional component
const ProductBrowse = props => {
const { id, name, img, store, price, desc, inCart } = props.product;
const [open, setOpen] = useState(false);
const classes = useStyles();
const openModal = () => {
setOpen(!open);
};
const closeModal = () => {
setOpen(!open);
};
return (
<Box border={1} borderRadius={3}>
<Card>
<CardActionArea>
{<ProductModal
open={open}
onClick={() => openModal()}
onClose={() => closeModal()}
onSave={() => closeModal()}
productName={name}
productDesc={desc}
/> }
<CardHeader title={name} subheader={formatCurrency(price)} />
<CardMedia image={img} alt={desc} />
<CardContent>
<Typography variant="body2" color="textSecondary" component="p">
{desc}
</Typography>
</CardContent>
</CardActionArea>
<CardActions>
<Button size="small" /*To Checkout*/>BUY NOW</Button>
<Button
size="small"
onClick={() => {
console.log("Added to Cart");
}}
>
ADD TO CART
</Button>
<Button size="small">REVIEW</Button>
</CardActions>
</Card>
</Box>
);
};
Also while using this components pass product as it's props as you are destructuring in ProductBrowse component. It should be like this:
<ProductBrowse products={this.products} />