Hi I have 2 components.
The first component provides a read (useSelector) from the Redux state object and renders its contents
The second component ensures the insertion of new data into the same Redux state object
How to achieve that when a Redux state object changes with the help of the second component, the first component captures this change and renders the new content of the object again.
I tried to add in the component element:
useEffect(() => {
...some actions
}, [reduxStateObject]);
But it gives me too many requests.
/// EDIT add real example
component
import React from "react";
import { useSelector } from "react-redux";
const ToDoList = () => {
const { todos } = useSelector((state) => state.global);
return (
<div>
<h1>Active</h1>
{todos
?.filter((todo) => !todo.isCompleted)
.sort((a, b) => (a.deadline < b.deadline ? 1 : -1))
.map((todo, id) => {
const date = new Date(todo.deadline).toLocaleString();
return (
<div key={id}>
<p>{todo.text}</p>
<p>{date}</p>
</div>
);
})}
</div>
);
};
export default ToDoList;
component
import React, { useEffect } from "react";
import { useDispatch } from "react-redux";
import { getToDoItems } from "../redux/globalSlice";
import ToDoList from "../components/ToDoList";
const ToDoWall = () => {
const dispatch = useDispatch();
useEffect(() => {
dispatch(getToDoItems(1));
}, [dispatch]);
const submitForm = (e) => {
dispatch(postToDoItem(e.data));
};
return (
<>
<ToDoList />
<form onSubmit={submitForm}>
<input type="text"></input>
<input type="submit" value="" />
</form>
</>
);
};
export default ToDoWall;
/// EDIT add Reducer
import { createSlice } from "#reduxjs/toolkit";
import axios from "axios";
const initialState = {
todos: null,
};
export const globalSlice = createSlice({
name: "global",
initialState,
reducers: {
setItems: (state, action) => {
state.todos = action.payload;
},
},
});
export const { setItems } = globalSlice.actions;
export default globalSlice.reducer;
// Load todo items
export const getToDoItems = (id) => {
return (dispatch) => {
axios
.get(`https://xxx.mockapi.io/api/list/${id}/todos`)
.then((resp) => dispatch(setItems(resp.data)));
};
};
// Post a list name
export const postNameList = (data) => {
return (dispatch) => {
axios.post("https://xxx.mockapi.io/api/list", {
name: data,
});
};
};
// Post a todo item
export const postToDoItem = (id, data) => {
return (dispatch) => {
axios.post(
`https://xxx.mockapi.io/api/list/${id}/todos`,
{
listId: id,
title: data.title,
text: data.text,
deadline: +new Date(data.deadline),
isCompleted: false,
}
);
};
};
As far as I understood, you don't need to do anything. When you dispatch action to change state in redux store, it'll change, and all components that use that state will get it, you don't need to worry about updating anything.
Related
I have a problem updating and displaying data from the api.
The problem occurs when I delete a specific item, sometimes the change is displayed (rendered) and other times not, until after the refresh.
Where can there be a mistake please?
Component
import React, { useEffect } from "react";
import { useParams } from "react-router-dom";
import { useSelector } from "react-redux";
import { useDispatch } from "react-redux";
import { getTodoItems, deleteTodoItem } from "../redux/todoSlice";
const TodoList = () => {
const { id } = useParams();
const dispatch = useDispatch();
const { todoList, urlParams } = useSelector((state) => state.todo);
useEffect(() => {
dispatch(getTodoItems(id));
dispatch(setUrlParams(+id));
}, [dispatch, id]);
const handleClick = (e) => {
dispatch(deleteTodoItem(urlParams, e.target.id));
dispatch(getTodoItems(urlParams));
};
return (
<div>
{todoList.map((todo, id) => {
<div key={id}>
<p>{todo.text}</p>
<button id={todo.id} onClick={handleClick}>
Delete
</button>
</div>;
})}
</div>
);
};
export default TodoList;
Slice
import { createSlice } from "#reduxjs/toolkit";
import axios from "axios";
const initialState = {
todoList: null,
urlParams: null,
};
export const todoSlice = createSlice({
name: "todo",
initialState,
reducers: {
setItems: (state, action) => {
state.todoList = action.payload;
},
setUrlParams: (state, action) => {
state.urlParams = action.payload;
},
},
});
export const { setItems, setUrlParams } = todoSlice.actions;
export default todoSlice.reducer;
// Load todo items
export const getTodoItems = (id) => {
return (dispatch) => {
axios
.get(`https://xxx.mockapi.io/api/list/${id}/todolist`)
.then((resp) => dispatch(setItems(resp.data)));
};
};
// Delete todo item
export const deleteTodoItem = (idList, idTodo) => {
return (dispatch) => {
axios
.delete(`https://xxx.mockapi.io/api/list/${idList}/todolist/${idTodo}`)
.then(
axios
.get(`https://xxx.mockapi.io/api/list/${idList}/todolist`)
.then((resp) => dispatch(setItems(resp.data)))
);
};
};
Thus, the first comes to download data from api -> render this data -> delete a specific item -> re-render the list without the deleted item
The problem occurs in the re-rendering, sometimes it happens that the data is erased in the background but the change is not apparent, but sometimes absolutely nothing happens, and the change occurs only after clicking the delete button again.
I am trying to use useReduce along with useContext, when I console the value in a reducer, I am getting array of an object in the console, but when I try to access the state from another component, I am getting the state is undefined. State.map() is completely empty.
Here is my code
App.js
import React, { createContext, useReducer, useEffect } from 'react';
import Uploadproject from './Uploadproject';
import Getprojects, { projectget } from './Getprojects';
import reducer from './reducer/Usereducer';
export const Contextstate = createContext();
const App = () => {
const initialvalue = null;
const [state, dispatch] = useReducer(reducer, initialvalue);
const updatestate = async () => {
const data = await projectget();
dispatch({ type: 'add', payload: 'from app.js' });
};
useEffect(() => {
updatestate();
}, []);
return (
<>
<Contextstate.Provider value={{ state, dispatch }}>
<Uploadproject />
<Getprojects />
</Contextstate.Provider>
</>
);
};
export default App;
Usereducer.js
import { projectget } from '../Getprojects';
const reducer = (state, action) => {
if (action.type === 'add') {
projectget().then((result) => {
console.log(result);
// state = [ ...result]
state = result;
console.log(state);
return state;
});
}
};
export default reducer;
Getprojects.js
import React, { useContext, useEffect, useState } from 'react';
import { Contextstate } from './App';
const Getprojects = () => {
const { state, dispatch } = useContext(Contextstate);
const getstate = () => {
dispatch({ type: 'add', payload: 'from getprojects' });
};
useEffect(() => {
getstate();
}, []);
console.log(state);
return (
<>
<div>
<h1>Projects</h1>
{state &&
state.map((cur) => {
return (
<div key={cur._id}>
<h1>{cur.title}</h1>
<p>{cur.description}</p>
<button
onClick={() => {
deletproject(cur._id);
}}
>
Delete
</button>
</div>
);
})}
</div>
</>
);
};
export default Getprojects;
When I try to access the state from Getprojects component, its value is undefined. But inside a reducer if I, console am getting an array of object. In any other component, the state is undefined.
Any idea???
If you want to handle asyn logic in your application by using redux, you should pick one of Async Redux Middleware packages
redux-thunk (more easiest to config and good for small projects)
redux-saga
redux-observable etc.
Or you can just use useEffect and dispatch only result actions to it. For example:
useEffect(() => {
dispatch(getProjectActionStart());
projectget()
.then((result) => {
console.log(result);
// state = [ ...result]
state = result;
console.log(state);
dispatch(getProjectActionStart(state));
return state;
})
.catch(() => {
dispatch(getProjectActionFailed());
});
}, []);
I'm learning redux hooks from library "react-redux" because I need to apply Redux also in the functional components of my project.
So far I don't understand how can be used the same project structure of the redux HOC with connect that I use for the class components.
Specifically I have a separate action file which invoke my API with axios:
FoosActions.js
import axios from "axios";
import {
GET_FOO,
} from "./Types";
};
export const getFoo = () => async (dispatch) => {
const res = await axios.get("/api/v1/foos");
dispatch({
type: GET_FOO,
payload: res.data,
});
};
FooList.js:
import { connect } from "react-redux";
import { getFoos } from "../../actions/FoosActions";
class FoosList extends Component {
constructor() {
super();
this.state = {
errors: {},
};
}
componentDidMount() {
this.props.getFoos();
}
render() {
const { data } = this.props.foo;
return (
<div className="container">
<h2>foo data fetched from API endpoint : </h2>
<ul>
{data.map((foo) => {
return (
<li>
{foo.id} - {foo.name}
</li>
);
})}
<ul>
</div>
</div>
</div>
);
}
}
const mapStateToProps = (state) => ({
foo: state.foo,
errors: state.errors,
});
export default connect(mapStateToProps, { getFoos })(FooList);
FooReducer,js
import { GET_FOO} from "../actions/Types";
const initialState = {
foos: [],
};
export default function (state = initialState, action) {
switch (action.type) {
case GET_FOO:
return {
...state,
foos: action.payload,
};
Now instead in my Functional Component:
FooListFC.js
import { useDispatch, useSelector } from "react-redux";
import { getFoo } from "../../actions/FoosActions";
const Mapping = (props) => {
const [foo, setFoo] = useState([]);
const dispatch = useDispatch();
useEffect(() => {
dispatch(getFoo());
const fooRetrieved = useSelector((state) => state.foo);
setFoo(fooRetrieved);
}, []);
return (
<div className="container">
<h2>foo data fetched from API endpoint : </h2>
<ul>
{foo.map((foo) => {
return (
<li>
{foo.id} - {foo.name}
</li>
);
})}
</ul>
</div>
)
}
How can I reproduce the same behavior of fetching data from API in class component with actions in a different file and using redux hooks (my code in the functional component is not working) ?
Is it a bad practice having both approaches in the same project?
you are able to reproduce the same behaviour, in the function component you can use the selector only instead of both useSelector and useState:
const Mapping = (props) => {
const foo = useSelector((state) => state.foo);
const dispatch = useDispatch();
useEffect(() => {
dispatch(getFoo());
}, []);
...
currently working on adding the items to cart using react and redux but the add item does not work
I'm taking the items from my collections page and then passing the key to the product preview page
I'm using react-redux cartReducer the three files are
just can't figure out how to pass the fish products
product page
cart actions
cart reducer
import React, { Component } from 'react';
import { Link } from 'react-router-dom';
import firebase from '../../firebase/firebase';
import { connect } from 'react-redux';
import { addItem } from '../../redux/cart/cart-actions'
class FishPage extends Component {
constructor(props) {
super(props);
this.ref = firebase.firestore().collection('fishproducts');
this.unsubscribe = null;
this.state = {
fishproducts: []
};
}
componentDidMount() {
const ref = firebase.firestore().collection('fishproducts').doc(this.props.match.params.id);
ref.get().then((doc) => {
if (doc.exists) {
this.setState({
fishproducts: doc.data(),
key: doc.id,
isLoading: false
});
} else {
console.log("No such document!");
}
});
}
render() {
return (
<div >
<div>
<div>
<h4><Link to="/">back</Link></h4>
<h3>
{this.state.fishproducts.name}
</h3>
</div>
<div >
<dl>
<dt>Description:</dt>
<dd>{this.state.fishproducts.description}</dd>
<dt>Discount:</dt>
<dd>{this.state.fishproducts.discount}</dd>
<dt>Size:</dt>
<dd>{this.state.fishproducts.size}</dd>
<dt>Weight:</dt>
<dd>{this.state.fishproducts.weight}</dd>
<dt>Price:</dt>
<dd>{this.state.fishproducts.price}</dd>
<dt>Stock:</dt>
<dd>{this.state.fishproducts.stock}</dd>
</dl>
<button onClick={() => addItem(this.state.fishproducts)} >ADD TO CART</button>
</div>
</div>
</div>
);
}
}
const mapDispatchToProps = dispatch => ({
addItem: item => dispatch(addItem(item))
})
export default connect(null, mapDispatchToProps)(FishPage);```
this is cart action page
```import CartActionTypes from './cart-types';
export const toggleCartHidden = () => ({
type:CartActionTypes.TOGGLE_CART_HIDDEN
});
export const addItem = item => ({
type: CartActionTypes.ADD_ITEM,
payload: item
})```
this is cart reducer
```import CartActionTypes from './cart-types';
const INITIAL_STATE = {
hidden: true,
cartItems: []
};
export const cartReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case CartActionTypes.TOGGLE_CART_HIDDEN:
return {
...state,
hidden: !state.hidden
};
case CartActionTypes.ADD_ITEM:
return {
...state,
//cartItems: addItem(state.cartItems, action.payload)
cartItems: [...state.cartItems,action.payload]
};
default:
return state;
}
}
export default cartReducer;```
cant figure out how to pass fishproducts
So concept of React is that you need to access Firebase with a function. For that you should use a functional component.
React allows Hooks to get access to your state without a constructor so that's all
and then you'll need to use dispatch.
import React, { useState, useEffect } from 'react';
import firebase from '../../firebase/firebase';
import { Link } from 'react-router-dom';
import { connect , useDispatch} from "react-redux";
import { addItem} from '../../redux/cart/cart-actions';
const FishPage = (props) => {
const [state, setState] = useState({
name: '',
… rest of the values
isLoading: true,
})
const { name, … rest of the values } = state;
useEffect(() => {
setState({ isLoading: true });
const ref = firebase.firestore().collection('fishproducts').doc(props.match.params.id);
ref.get().then((doc) => {
setState({
name: doc.data().name,
… rest of the values
isLoading: false,
});
})
}, [props.match.params.id])
const item = [];
const dispatch = useDispatch();
return (
<div >
<div>
//your body here
<button onClick={() => dispatch(addItem(item))} >ADD TO CART</button>
</div>
</div>
</div>
);
}
const mapDispatchToProps = dispatch => {
return{
addItem: (item) => dispatch(addItem(item))
}
}
export default connect(null, mapDispatchToProps)(FishPage)
I've created a simple to do app using React. I've attempted to persist state using local storage. However, the local storage code I've added is somehow preventing my components from rendering altogether. Not only are the todos saved in state not appearing, none of my components will render. I get a blank page on refresh. Can someone help me figure out what's wrong?
Here's what happens on the initial save after the local storage code is included. It loads the components just fine, but the to dos that are already in state are not shown:
After using the form to add to dos and refreshing the page, this happens. None of the components are shown whatsoever. Just a blank page.
Here is the local storage code inside my index.js file. I'm pretty sure the problem is here but I have included the code for the other components and the reducer as well:
const persistedState = localStorage.getItem('state') ? JSON.parse(localStorage.getItem('state')) : [];
const store = createStore(reducer, persistedState);
store.subscribe(() => {
localStorage.setItem('state', JSON.stringify(store.getState()));
})
The index.js file in its entirety:
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from "redux";
import { Provider } from "react-redux";
import './index.css';
import App from './App';
import { reducer } from "./reducers/todoReducer";
import * as serviceWorker from './serviceWorker';
const persistedState = localStorage.getItem('state') ? JSON.parse(localStorage.getItem('state')) : [];
const store = createStore(reducer, persistedState);
store.subscribe(() => {
localStorage.setItem('state', JSON.stringify(store.getState()));
})
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
the other components:
TodoList.js:
import Todo from "./Todo";
const TodoList = props => {
return (
<ul className="task-list">
{props.state.map(task => (
<Todo task={task} />
))}
</ul>
)
}
const mapStateToProps = state => {
return {
state: state
}
}
export default connect(mapStateToProps)(TodoList);
TodoForm.js:
const TodoForm = props => {
const [newItemText, setNewItemText] = useState("");
const handleChanges = e => {
e.preventDefault();
setNewItemText(e.target.value);
};
const saveState = () => localStorage.setItem("props.state", JSON.stringify(props.state));
useEffect(() => {
const todos = localStorage.getItem('state');
if (todos) props.setState({ [props.state]: JSON.parse(props.state) })
}, [])
return (
<div className="form-div">
<input
className="add-input"
name="todo"
type="text"
placeholder="enter a task"
value={newItemText}
onChange={handleChanges}
/>
<button
className="add-button"
onClick = {e => {
e.preventDefault();
props.addItem(newItemText);
saveState();
}}>Add a Task
</button>
<button
className="add-button"
onClick={e => {
e.preventDefault();
props.removeCompleted();
}}>Remove Completed
</button>
</div>
)
}
const mapStateToProps = state => {
return {
state: state
}
}
export default connect(mapStateToProps, {addItem, removeCompleted})(TodoForm);
Todo.js:
const Todo = props => {
return (
<li
className="tasks"
style={{textDecoration: props.task.completed ? 'line-through' : 'none'}}
onClick={() => props.toggleCompleted(props.task.id)}>
{props.task.item}
</li>
)
}
const mapStateToProps = state => {
return {
state: state
}
}
export default connect(mapStateToProps, {toggleCompleted})(Todo);
todoReducer.js:
export const initialState = [
{ item: 'Learn about reducers', completed: false, id: 1 },
{ item: 'review material from last week', completed: false, id: 2 },
{ item: 'complete reducer todo project', completed: false, id: 3 }
]
export const reducer = (state = initialState, action) => {
switch(action.type) {
case ADD_ITEM:
// console.log(action.payload)
return [
...state,
{
item: action.payload,
completed: false,
id: Date.now()
}
]
case TOGGLE_COMPLETED:
const toggledState = [...state];
toggledState.map(item => {
if(item.id === action.payload) {
item.completed = !item.completed;
}
})
console.log(toggledState);
state = toggledState;
return state;
case REMOVE_COMPLETED:
return state.filter(item => !item.completed);
default:
return state;
}
}
export default reducer;
App.js:
import React from 'react';
import './App.css';
// components
import TodoList from "./components/TodoList";
import TodoForm from "./components/TodoForm";
function App() {
return (
<div className="App">
<h1 className="title">To Do List</h1>
<TodoList />
<TodoForm />
</div>
);
}
export default App;
actions.js:
export const ADD_ITEM = 'ADD_ITEM';
export const TOGGLE_COMPLETED = 'TOGGLE_COMPLETED';
export const REMOVE_COMPLETED = 'REMOVE_COMPLETED';
export const addItem = input => {
return {
type: ADD_ITEM, payload: input
}
};
export const toggleCompleted = (id) => {
return {
type: TOGGLE_COMPLETED, payload: id
}
};
export const removeCompleted = () => {
return {
type: REMOVE_COMPLETED
}
};