import produce from "immer";
const initialState = {
isLoading: true,
error: "",
burgers: [],
};
export default function (state = initialState, action) {
switch (action.type) {
case "ADD_BURGER_BUCKET": {
return produce(state, (draftState) => {
if (Array.isArray(action.payload)) {
draftState.burgers.push(...action.payload);
} else {
draftState.burgers.push(action.payload);
}
});
}
case "REMOVE_BURGERS_BUCKET": {
return produce(state, (draftState) => {
draftState.burgers = []
});
}
**case "REMOVE_ONE_BURGER_BUCKET": {
return produce(state, (draftState) => {
console.log(action.payload, draftState.burgers) console.log => 3 Proxy {0: {…}}
draftState.burgers.filter(el => el.id !== action.payload)
})
}** HERE THIS ONE DOES NOT WORK!!!
default:
return state;
}
}
return ( <===== BURGER BUTTON
<Burger
key={burger.id}
text={burger.name}
cost={burger.cost}
onClick={() => {
dispatch({
type: "REMOVE_ONE_BURGER_BUCKET",
payload: burger.id, <=== PASS ID TO REDUCER
}); <==== THIS ONE DOESN'T REMOVE THE ELEMENT FROM AN ARRAY
localStorage.setItem("burger", JSON.stringify(burger));
localStorage.setItem(
"burgersBucket",
JSON.stringify(
list.burgers.filter((el) => el.id !== burger.id)
)
);
history.push("/redo");
}}
/>
);
}
I want to remove element from array by its id, but I can't do it, that's what I get in the console
3 Proxy {0: {…}}
I have useselector and useDispatch hooks both imported.
FULL CODE
import React from "react";
import { Wrapper } from "../../styled/general";
import { Menu, MenuTitle, Burgers } from "../home/homestyled";
import Burger from "../../component/burger";
import { useDispatch, useSelector } from "react-redux";
import { useEffect } from "react";
import { useHistory } from "react-router-dom";
export default function Index() {
const list = useSelector((state) => state.burgerBucket);
const dispatch = useDispatch();
const history = useHistory();
useEffect(() => {
console.log(list.burgers);
if (list.burgers.length > 0) {
localStorage.setItem("burgersBucket", JSON.stringify(list.burgers));
} else {
let burgersBucket = JSON.parse(localStorage.getItem("burgersBucket"));
dispatch({ type: "ADD_BURGER_BUCKET", payload: burgersBucket });
}
}, []);
return (
<Wrapper>
<Menu>
<MenuTitle>My Bucket</MenuTitle>
<Burgers>
{[...list.burgers, { finish: true }, { addMore: true }].map(
(burger) => {
if (burger.addMore) {
return (
<Burger
key={-2}
bg={"lightgreen"}
text={"Додати ще"}
onClick={() => {
history.push("/");
}}
/>
);
}
if (burger.finish) {
return (
<Burger
key={-1}
bg={"#ff5050"}
text={"Завершити"}
onClick={() => {
dispatch({ type: "REMOVE_BURGERS_BUCKET" });
history.push("/");
}}
/>
);
}
return (
<Burger
key={burger.id}
text={burger.name}
cost={burger.cost}
onClick={() => {
dispatch({
type: "REMOVE_ONE_BURGER_BUCKET",
payload: burger.id,
});
localStorage.setItem("burger", JSON.stringify(burger));
localStorage.setItem(
"burgersBucket",
JSON.stringify(
list.burgers.filter((el) => el.id !== burger.id)
)
);
history.push("/redo");
}}
/>
);
}
)}
</Burgers>
</Menu>
</Wrapper>
);
}
enter code here
FULL CODE REDUCER
import produce from "immer";
const initialState = {
isLoading: true,
error: "",
burgers: [],
};
export default function (state = initialState, action) {
switch (action.type) {
case "ADD_BURGER_BUCKET": {
return produce(state, (draftState) => {
if (Array.isArray(action.payload)) {
draftState.burgers.push(...action.payload);
} else {
draftState.burgers.push(action.payload);
}
});
}
case "REMOVE_BURGERS_BUCKET": {
return produce(state, (draftState) => {
draftState.burgers = []
});
}
case "REMOVE_ONE_BURGER_BUCKET": {
return produce(state, (draftState) => {
console.log(action.payload, draftState.burgers)
draftState.burgers.filter(el => el.id !== action.payload)
})
}
default:
return state;
}
}
ALSO MAYBE THIS ONE WILL BE IMPORTANT, THIS IS THE CODE(PAGE) I GET REDIRECTED ONCE THE USER CLICKED THAT BURGER-BUTTON
FULL CODE
import React, { useEffect, useState, useContext } from "react";
import { Wrapper } from "../../styled/general";
import { Menu, MenuTitle } from "../home/homestyled";
import {
BurgerIngridients,
IngridientWrapper,
BurgerDetails,
DetailsTitle,
IngridientsDetails,
Total,
DetailsButtonContinue,
} from "./burgerredostyled";
import Ingridient from "../../component/Ingridient";
import IngridientdetailBlock from "../../component/IngridientsDetailBlock";
import { useHistory } from "react-router-dom";
import { useDispatch, useSelector } from "react-redux";
import { useBurger } from "../../hooks/useBurger";
import { sumOfToppings } from "../../helpers/sum";
export default function Index() {
const burger = useSelector((state) => state.burger);
const [toppings, error, isLoading] = useBurger(
"http://localhost:3000/toppings"
);
const dispatch = useDispatch();
const history = useHistory();
useEffect(() => {
if (burger.id) {
localStorage.setItem("burger", JSON.stringify(burger));
} else {
let localStorageBurger = JSON.parse(localStorage.getItem("burger"));
dispatch({ type: "ADD_BURGER", payload: localStorageBurger });
}
for (let i = 0; i < toppings.length; i++) {
for (let j = 0; j < burger.consists_of.length; j++) {
if (burger.consists_of[j].name === toppings[i].name) {
toppings[i].quantity = burger.consists_of[j].quantity;
}
}
}
if (toppings.length > 0) {
dispatch({
type: "ADD_BURGER",
payload: { ...burger, consists_of: toppings },
});
}
}, [isLoading]);
const add = (text, num) => {
for (let i = 0; i < toppings.length; i++) {
if (toppings[i].name === text) {
toppings[i].quantity = num;
}
}
dispatch({
type: "ADD_BURGER",
payload: { ...burger, consists_of: toppings },
});
localStorage.setItem(
"burger",
JSON.stringify({ ...burger, consists_of: toppings })
);
};
if (!isLoading)
return (
<Wrapper>
<Menu>
<MenuTitle>{burger && burger.name}</MenuTitle>
<IngridientWrapper>
<BurgerIngridients>
{burger.consists_of.map(({ name, quantity, id }) => {
return (
<Ingridient
key={id}
text={name}
add={add}
initValue={quantity}
/>
);
})}
</BurgerIngridients>
<BurgerDetails>
<DetailsTitle>
<span>{burger && burger.name} | інформація</span>{" "}
<DetailsButtonContinue
onClick={() => {
dispatch({
type: "ADD_BURGER_BUCKET",
payload: { ...burger, cost: sumOfToppings(burger) },
});
history.push("/bucket");
}}
>
продовжити
</DetailsButtonContinue>
</DetailsTitle>
<IngridientsDetails>
{burger.consists_of
.filter((el) => el.quantity > 0)
.map((el) => {
return (
<IngridientdetailBlock
key={el.id}
text={el.name}
price={el.quantity * el.cost}
qty={el.quantity}
></IngridientdetailBlock>
);
})}
</IngridientsDetails>
<Total>Загалом {sumOfToppings(burger)}(грн.)</Total>
</BurgerDetails>
</IngridientWrapper>
</Menu>
</Wrapper>
);
if (isLoading) {
return <span>loading</span>;
}
}
Array.prototype.filter does not mutate the array, it creates a new one.
So this:
draftState.burgers.filter(el => el.id !== action.payload)
is not actually changing draftState.burgers. But this will:
produce(state, (draftState) => {
draftState.burgers = draftState.burgers.filter(el => el.id !== action.payload)
})
Related
I using Context.Provider + useReducer, i have function "fetchCars()" in my context for fetching cars which depends on selected filter value
May be noob question, but i can't understand why consumer-component named "Filters.jsx" is mounting every time after i changed filter value. And because of this i cant save values in useState of Filter.jsx component
Codesandbox link
https://codesandbox.io/s/peaceful-morse-21zj6m?file=/src/components/Filters.jsx
in Codesandbox you can see console print when filter changed
CarsContextProvider.jsx
import { useReducer, createContext, useCallback } from "react";
export const CarsContext = createContext()
const getCarsFromServer = (status) => {
// dummy fetch
const dataFromServer = [
{ id: 1, name: 'Volvo', status: 'notAvailable' },
{ id: 2, name: 'BMW', status: 'inStock' },
{ id: 3, name: 'Mercedes', status: 'notAvailable' },
{ id: 4, name: 'Audi', status: 'notAvailable' },
{ id: 5, name: 'Opel', status: 'inStock' },
{ id: 6, name: 'Renault', status: 'inStock' },
]
return new Promise((resolve) => {
setTimeout(() => {
if (status === 'all') {
return resolve(dataFromServer)
}
resolve(dataFromServer.filter(item => item.status === status))
}, 500);
})
}
const reducer = (state, action) => {
switch (action.type) {
case 'pending':
return { ...state, loading: true }
case 'success':
return { ...state, loading: false, items: action.payload }
case 'error':
return { ...state, loading: false, error: action.payload }
case 'setFilter':
return { ...state, statusFilter: action.payload }
default:
break;
}
}
const initState = {
items: [],
loading: false,
error: '',
statusFilter: 'all',
}
const CarsContextProvider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initState)
const fetchCars = useCallback(async () => {
try {
dispatch({ type: 'pending' })
const data = await getCarsFromServer(state.statusFilter)
dispatch({ type: 'success', payload: data })
} catch (error) {
dispatch({ type: 'error', payload: error })
}
}, [state.statusFilter])
return (
<CarsContext.Provider value={{ state, dispatch, fetchCars }}>
{children}
</CarsContext.Provider>
)
}
export default CarsContextProvider
App.jsx
import CarsScreen from "./components/CarsScreen";
import CarsContextProvider from "./context/CarsContext";
function App() {
return (
<CarsContextProvider>
<CarsScreen />
</CarsContextProvider>
);
}
export default App;
CarsScreen.jsx
import CarsList from "./CarsList"
const CarsScreen = () => {
return (
<div>
<CarsList />
</div>
)
}
export default CarsScreen
CarsList.jsx
import { useContext, useEffect } from "react"
import { CarsContext } from "../context/CarsContext"
import Filters from "./Filters"
const CarsList = () => {
const { state, fetchCars } = useContext(CarsContext)
useEffect(() => {
fetchCars()
}, [fetchCars])
if (state.loading) return <h3>loading...</h3>
return (
<>
<Filters />
<hr />
<ul>
{state.items.map((car => <li key={car.id}>{car.name}</li>))}
</ul>
</>
)
}
export default CarsList
Filters.jsx
import { useState, useEffect, useContext } from "react"
import { CarsContext } from "../context/CarsContext"
const Filters = () => {
const [localState, setLocalState] = useState('init')
const { state, dispatch } = useContext(CarsContext)
useEffect(() => {
// There is my question! Why console.log executing every time i change filter select option ?
console.log('component mounted');
}, [])
const filterChangeHandler = (e) => {
//and also localState could not change, because this component every time mounts with init value
setLocalState('filter changed')
// this dispatch changes filter value, and items fetching from server
dispatch({ type: 'setFilter', payload: e.target.value })
}
return (
<div>
<select
name="stockFilter"
onChange={filterChangeHandler}
defaultValue={state.statusFilter}
>
<option value="all">show all</option>
<option value="inStock">in stock</option>
<option value="notAvailable">not available</option>
</select>
<p>Filters local state is : {localState}</p>
</div>
)
}
export default Filters
Codesandbox link
https://codesandbox.io/s/peaceful-morse-21zj6m?file=/src/components/Filters.jsx
Tried to comment React.StrictMode line in index.js., but no effect
Is it possible to avoid this unwanted mount Filters.jsx component?
Found a problem.
Problem was in component GoodsList.jsx loading status was dismounting Filter.jsx
if (state.loading) return <h3>loading...</h3>
fixed version of GoodsList.jsx
import { useContext, useEffect } from "react"
import { GoodsContext } from "../context/GoodsContextProvider"
import GoodsFilter from "./GoodsFilter"
const GoodsList = () => {
const { state, fetchGoods } = useContext(GoodsContext)
useEffect(() => {
fetchGoods()
}, [fetchGoods])
return (
<>
<GoodsFilter />
{state.loading ? <h3>loading...</h3>
:
<>
<hr />
<ul>
{state.items.map((good => <li key={good.id}>{good.name} - {good.quantity}</li>))}
</ul>
</>
}
</>
)
}
export default GoodsList
I'm trying to use a custom hook that bring me functions to handle my TODOS on my context, but it gives me an error
Uncaught TypeError: useContext(...) is undefined
The above error occurred in the component:
Complete Error Image
TodoProvider.jsx
import { useReducer } from 'react';
import { useTodos } from '../hooks/useTodos';
import { TodoContext, todoReducer } from './';
export const TodoProvider = ({ children }) => {
const init = () => {
return [];
};
const [todos, dispatchTodos] = useReducer(todoReducer, {}, init);
const { handleNewTodo, handleToggleTodo } = useTodos();
return (
<TodoContext.Provider
value={{ todos, dispatchTodos, handleNewTodo, handleToggleTodo }}
>
{children}
</TodoContext.Provider>
);
};
useTodos.js
import { useContext } from 'react';
import { TodoContext } from '../context';
import { types } from '../types/types';
export const useTodos = () => {
const { dispatchTodos } = useContext(TodoContext);
const handleNewTodo = todo => {
const action = {
type: types.add,
payload: todo,
};
dispatchTodos(action);
};
const handleToggleTodo = id => {
dispatchTodos({
type: types.toggle,
payload: id,
});
};
return { handleNewTodo, handleToggleTodo };
};
The error traceback in your image says
`useContext(...)` is not defined
useTodos (useTodos.js:6)
Since you aren't showing your useTodos.js file, I must rely on my crystal ball to tell me that you've forgotten to
import {useContext} from 'react';
in useTodos.js, hence "not defined".
Here's an one-file example based on your code that verifiably does work...
import { useReducer, useContext, createContext } from "react";
function todoReducer(state, action) {
switch (action.type) {
case "add":
return [...state, { id: +new Date(), text: action.payload }];
default:
return state;
}
}
const TodoContext = createContext([]);
const TodoProvider = ({ children }) => {
const [todos, dispatchTodos] = useReducer(todoReducer, null, () => []);
return (
<TodoContext.Provider value={{ todos, dispatchTodos }}>
{children}
</TodoContext.Provider>
);
};
function useTodoActions() {
const { dispatchTodos } = useContext(TodoContext);
function handleNewTodo(todo) {
dispatchTodos({
type: "add",
payload: todo
});
}
function handleToggleTodo(id) {
dispatchTodos({
type: "toggle",
payload: id
});
}
return { handleNewTodo, handleToggleTodo };
}
function useTodos() {
return useContext(TodoContext).todos;
}
function TodoApp() {
const todos = useTodos();
const { handleNewTodo } = useTodoActions();
return (
<div>
{JSON.stringify(todos)}
<hr />
<button onClick={() => handleNewTodo((+new Date()).toString(36))}>
Add todo
</button>
</div>
);
}
export default function App() {
return (
<TodoProvider>
<TodoApp />
</TodoProvider>
);
}
I have the error:
TypeError: dispatch is not a function
in this line of code dispatch(addNewNote(response.data.message));
I don't know why this error is happening, since I have other dispatches in other functions within the thunks.js file working just fine.
I really need help, any advice would be appreciated.
Please consider the code below:
binnacleReducer.js
import { ADD_NEW_NOTE } from "../Actions/binnacleActions";
export const binnacleNotesReducer = (state = [], action) => {
const { type, payload } = action;
switch (type) {
case ADD_NEW_NOTE:
const { binnacleNote } = payload;
return state.concat(binnacleNote);
default:
return state;
}
};
binnacleActions.js
//Action creators and actions
export const ADD_NEW_NOTE = "ADD_NEW_NOTE";
export const addNewNote = (binnacleNote) => ({
type: ADD_NEW_NOTE,
payload: {
binnacleNote,
},
});
thunks.js
import axios from "axios";
import { loginSuccess, loginFailed } from "../Actions/authActions";
import {
setOwnerSetup,
setSuperSetup,
loadBinnacleNotes,
binnacleNoteReview,
addNewNote,
} from "../Actions/binnacleActions";
export const addBinnacleNote = (
binnacle,
noteNumber,
binnacleNote,
responsible,
file
) => async (dispatch, getState) => {
try {
const bodyToSend = new FormData();
bodyToSend.append("binnacle", binnacle);
bodyToSend.append("binnacleNote", binnacleNote);
bodyToSend.append("noteNumber", noteNumber);
bodyToSend.append("responsible", responsible);
if (file) {
for (let x = 0; x < file.length; x++) {
bodyToSend.append(`file[${x}]`, file[x]);
}
}
const response = await axios.post(
"http://localhost:5000/addBinnacleNote",
bodyToSend,
{
headers: {
"Content-Type": "application/json",
},
}
);
dispatch(addNewNote(response.data.message));
} catch (e) {
alert("Thunk error in addNote: " + e);
}
};
EDIT:
BinnacleForm.js
import React, { useState, useEffect } from "react";
import { connect } from "react-redux";
import { addBinnacleNote } from "../../Store/Thunks/thunks";
import Grid from "#material-ui/core/Grid";
import Typography from "#material-ui/core/Typography";
import TextField from "#material-ui/core/TextField";
import Container from "#material-ui/core/Container";
import { Button } from "#material-ui/core";
import AccordionTest from "./AccordionTest";
var noteNumber;
function BinnacleForm({ authReducer, binnacleNotesReducer }) {
const [binnacleNote, setBinnacleNote] = useState(null);
const [file, setFile] = useState(null);
const responsible = authReducer.role;
const binnacle = authReducer.binnacle;
const setBinnacleId = () => {
if (binnacleNotesReducer !== [] || binnacleNotesReducer.length === 0) {
noteNumber = 1;
} else {
noteNumber = binnacleNotesReducer[binnacleNotesReducer.length - 1].id + 1;
}
};
setBinnacleId();
return (
<React.Fragment>
<Container maxWidth="md">
<Typography variant="h6" gutterBottom>
Agregar Nota
</Typography>
{/* <AccordionTest /> */}
<Grid container spacing={3}>
<Grid item xs={12} sm={9}>
<TextField
required
id="binnacle_note"
name="binnacle_note"
fullWidth
autoComplete="given-name"
helperText="Nota"
onChange={(e) => setBinnacleNote(e.target.value)}
/>
</Grid>
<Grid item xs={12} sm={3}>
<Button variant="contained" component="label">
Adjuntar Archivo
<input
onChange={(e) => setFile(e.target.files)}
type="file"
hidden
multiple
/>
</Button>
</Grid>
<Button
variant="contained"
color="primary"
onClick={addBinnacleNote(
binnacle,
noteNumber,
binnacleNote,
responsible,
file
)}
>
Agregar Nota
</Button>
</Grid>
</Container>
</React.Fragment>
);
}
const mapStateToProps = (state) => ({
binnacleReducer: state.binnacleReducer,
authReducer: state.authReducer,
binnacleNotesReducer: state.binnacleNotesReducer.binnacleNotes,
});
const mapDispatchToProps = (dispatch) => ({
addBinnacleNote: (binnacle, binnacleNote, responsible) =>
dispatch(addBinnacleNote(binnacle, binnacleNote, responsible)),
});
export default connect(mapStateToProps, mapDispatchToProps)(BinnacleForm);
EDIT 2:
BinnacleForm.js
import React, { useEffect } from "react";
import { connect } from "react-redux";
import SetupBinnacleSuper from "../Binnacle/SetupBinnacleSuper";
import SetupBinnacleOwner from "../Binnacle/SetupBinnacleOwner";
import {
getBinnacleStatus,
getBinnacleStatusBySuper,
} from "../../Store/Thunks/thunks";
import BinnacleNotesList from "../Binnacle/BinnacleNotesList";
const Binnacle = ({
authReducer,
binnacleReducer,
getBinnacleStatus,
getBinnacleStatusBySuper,
}) => {
const binnacle = authReducer.binnacle;
const renderConditionaly = () => {
if (authReducer.role == "owner" && binnacleReducer.binnacleIsActive == 0) {
return <SetupBinnacleOwner />;
} else if (
authReducer.role == "owner" &&
binnacleReducer.binnacleIsActive == 1
) {
console.log("If Owner");
return <div>Owner</div>;
} else if (
authReducer.role == "super" &&
binnacleReducer.binnacleIsActiveBySuper == 0
) {
return <SetupBinnacleSuper />;
} else if (
authReducer.role == "super" &&
binnacleReducer.binnacleIsActiveBySuper == 1
) {
return <BinnacleNotesList />;
} else if (authReducer.role == "dro") {
return <BinnacleNotesList />;
} else if (authReducer.role == "constructor") {
return <BinnacleNotesList />;
}
};
useEffect(() => {
getBinnacleStatus(binnacle);
getBinnacleStatusBySuper(binnacle);
}, []);
return (
<div>
<h2>Bienvenido {authReducer.email}</h2>
{renderConditionaly()}
</div>
);
};
const mapStateToProps = (state) => ({
authReducer: state.authReducer,
binnacleReducer: state.binnacleReducer,
});
const mapDispatchToProps = (dispatch, getState) => ({
getBinnacleStatus: (binnacle) => dispatch(getBinnacleStatus(binnacle)),
getBinnacleStatusBySuper: (binnacle) =>
dispatch(getBinnacleStatusBySuper(binnacle)),
});
export default connect(mapStateToProps, mapDispatchToProps)(Binnacle);
binnacleReducer.js
export const binnacleReducer = (state = [], action) => {
const { type, payload } = action;
switch (type) {
case SET_OWNER_SETUP:
const { binnacleIsActive } = payload;
return {
...state,
binnacleIsActive,
};
case SET_SUPER_SETUP:
const { binnacleIsActiveBySuper } = payload;
return {
...state,
binnacleIsActiveBySuper,
};
default:
return state;
}
};
binnacleActions.js
export const SET_SUPER_SETUP = "SET_SUPER_SETUP";
export const setSuperSetup = (binnacleIsActiveBySuper) => ({
type: SET_SUPER_SETUP,
payload: {
binnacleIsActiveBySuper,
},
});
thunks.js
import axios from "axios";
import { loginSuccess, loginFailed } from "../Actions/authActions";
import {
setOwnerSetup,
setSuperSetup,
loadBinnacleNotes,
binnacleNoteReview,
addNewNote,
} from "../Actions/binnacleActions";
export const getBinnacleStatusBySuper = (binnacle) => async (
dispatch,
getState
) => {
try {
const response = await axios.get(
`http://localhost:5000/getBinnacleStatusBySuper?binnacle=${binnacle}`
);
dispatch(
setSuperSetup(response.data.binnacleIsActiveBySuper.is_active_by_super)
);
} catch (e) {
alert(e);
}
};
The issue is that you're not dispatching the action returned by addBinnacleNote. And the onClick prop should be a function.
You've defined the mapDispatchToProps function to return an addBinnacleNote prop which dispatches the action but you're not using it inside the component. You're calling the imported addBinnacleNote function instead. There are a couple of ways to solve it. You could remove the mapDispatchToProps function and use useDispatch to get access to the dispatch function and dispatch the action inside the onClick handler.
import { useDispatch } from 'react-redux'
const dispatch = useDispatch()
<Button
variant="contained"
color="primary"
onClick={() =>
dispatch(
addBinnacleNote(binnacle, noteNumber, binnacleNote, responsible, file)
)
}
>
Agregar Nota
</Button>
Or you could use the addBinnacleNote prop inside the onClick handler.
reducer
import { COMBINE_POST } from '../Actions/actionType'
const initialState = {
posts: null,
users: null,
comments: null,
post: null
}
export const rootReducer = (state = initialState, action) => {
switch (action.type) {
case 'getPosts':
return {
...state,
posts: action.data
}
case 'getUsers':
return {
...state,
users: action.data
}
case 'getComments':
return {
...state,
comments: action.data
}
case COMBINE_POST :
return {
...state,
post: action.payload
}
case 'removePost':
console.log('removepost', state.post.post)
return {
...state,
post: state.post.post.filter(item => item.id !==
action.payload)
}
default:
return state
}
}
action
import { COMBINE_POST } from './actionType'
export const fetchPosts = () => {
return dispatch => {
fetch(`https://jsonplaceholder.typicode.com/posts/`)
.then(res => res.json())
.then(res => dispatch({ type: 'getPosts', data: res }))
}
}
export const fetchUsers = () => {
return dispatch => {
fetch(`https://jsonplaceholder.typicode.com/users/`)
.then(res => res.json())
.then(res => dispatch({ type: 'getUsers', data: res }))
}
}
export const fetchComments = () => {
return dispatch => {
fetch(`https://jsonplaceholder.typicode.com/comments/`)
.then(res => res.json())
.then(res => dispatch({ type: 'getComments', data: res }))
}
}
export const removePost = id => {
return dispatch => {
fetch(`https://jsonplaceholder.typicode.com/posts/${id}`, {
method: 'DELETE',
})
dispatch({ type: 'removePost', payload: id })
}
}
export const combimePost = arr => ({ type: COMBINE_POST, payload: arr })
component render
import React, { useEffect } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { fetchPosts, fetchUsers, fetchComments, removePost } from '../../Redux/Actions/action'
import { combimePost } from '../../Redux/Actions/action'
import './newsList.scss'
export const NewsList = () => {
const dispatch = useDispatch()
const selector = useSelector(state => state.rootReducer)
useEffect(() => {
dispatch(
fetchPosts()
)
}, [])
useEffect(() => {
dispatch(
fetchUsers()
)
}, [])
useEffect(() => {
dispatch(
fetchComments()
)
}, [])
This is where I combine the post and user id
useEffect(() => {
const combinePost = selector.posts?.map(post => ({
...post,
user: selector.users?.find(user => post.userId === user.id),
commetn: selector?.comments?.find(comment => post.id === comment.postId)
}))
return dispatch(
combimePost(
{
post: combinePost,
}
)
)
}, [selector.posts, selector.users, selector.comments])
return <>
{selector?.post?.post?.map((res, i) => (
<div className="card text-center" key={i}>
<div className="card-header">
{res.user?.name}
</div>
<div className="card-body">
<h5 className="card-title">{res.title}</h5>
<p className="card-text">{res.comment?.body}</p>
<button
className="btn btn-outline-danger"
onClick={() => dispatch(removePost(res.id))}
>DELETE</button>
</div>
<div className="card-footer text-muted">
</div>
</div>
)
)}
</>
}
When a post is deleted, all posts disappear from the page, in the console it shows that the selected post has been deleted, but the page is not rendered, it becomes empty. what is the problem ?
When a post is deleted, all posts disappear from the page, in the console it shows that the selected post has been deleted, but the page is not rendered, it becomes empty. what is the problem ?
I need your help. I'm creating an app with useContext and useReducer hooks and I a have problems. I have a function to get all notes from my database. I called that function inside off useEffect hook:
import React, { useContext, useEffect } from "react";
import { useTranslation } from "react-i18next";
//Context
import AuthContext from "../../context/auth/authContext";
import NoteContext from '../../context/notes/noteContext';
//Components
import { Row, Col, Container, Button } from "react-bootstrap";
import Canva from '../Canva/Canva';
import Note from '../Note/Note';
const Dashboard = () => {
const { t, i18n } = useTranslation();
const authContext = useContext(AuthContext);
const { authUser, user } = authContext;
const noteContext = useContext(NoteContext);
const { notes, getNotes, addNote } = noteContext;
useEffect(() => {
getNotes();
}, []);
return (
<>
<Container>
<Row>
<Col sm={12} md={10}>
<Button onClick={() => addNote()} type='button' className='mb-2'>
Añadir elemento
</Button>
<Canva>
{notes && (notes.map(note => {
return (
<Note key={note._id} note={note} />
)
}))}
</Canva>
</Col>
</Row>
</Container>
</>
);
};
export default Dashboard;
If I called that function that way, my state doesn't change:
notes: undefined
But if I introduce a dependency inside of useEffect, my app goes into an infinite loop. For example:
useEffect(() => {
getNotes();
}, [notes])
//Or:
useEffect(() => {
getNotes()
}, [getNotes])
How can I avoid the infinite loop?
You need to use 2 useEffect hooks, one for fetch data and second to proceed it:
useEffect(() => {
getNotes();
}, []);
useEffect(() => {
if (notes && notes.length) {
....setState or what else
}
}, [notes]);
My note state looks like:
import React, { useReducer } from 'react';
import clientAxios from '../../config/clientAxios';
import NoteContext from './noteContext';
import NoteReducer from './noteReducer';
import {
GET_NOTES,
ADD_NOTE,
DELETE_NOTE,
UPDATE_NOTE,
} from '../../types';
const NoteState = ({ children }) => {
const initialState = {
notes: [],
noteError: false,
};
const [state, dispatch] = useReducer(NoteReducer, initialState);
const getNotes = async () => {
try {
const response = await clientAxios.get('/user/Notes');
dispatch({
type: GET_NOTES,
payload: response.data
})
} catch (error) {
console.log(error.response);
}
}
const addNote = async data => {
try {
const response = await clientAxios.post('/addNote', data);
dispatch({
type: ADD_NOTE,
payload: response.data.data
})
} catch (error) {
console.log(error.response);
}
}
const updateNote = async (id, { title, description }) => {
try {
const response = await clientAxios.put(`updateNote/${id}`, { title, description });
console.log(response.data);
dispatch({
type: UPDATE_NOTE,
payload: response.data
})
} catch (error) {
console.log(error.response)
}
}
const deleteNote = async id => {
try {
await clientAxios.put(`/deleteNote/${id}`);
dispatch({
type: DELETE_NOTE,
payload: id
})
} catch (error) {
console.log(error.response);
}
}
return(
<NoteContext.Provider
value={{
notes: state.notes,
noteError: state.noteError,
getNotes,
addNote,
updateNote,
deleteNote,
}}
>
{children}
</NoteContext.Provider>
);
}
export default NoteState;
and my reducer:
import {
GET_NOTES,
ADD_NOTE,
DELETE_NOTE,
UPDATE_NOTE,
} from '../../types';
export default (action, state) => {
switch(action.type) {
case GET_NOTES:
return {
...state,
notes: action.payload
}
case ADD_NOTE:
return {
...state,
notes: [...state.notes, action.payload]
}
case UPDATE_NOTE:
return {
...state,
notes: state.notes.map(note => note._id === action.payload._id ? action.payload : note)
}
case DELETE_NOTE:
return {
...state,
notes: state.notes.filter(note => note._id !== action.payload),
}
default:
return state;
}
}