I have a problem, so I am trying to put two or more "dispatch" in my application
but I don't know why it is just working one dispatch that I put in last
import axios from "axios";
const setDataBlog = (page) =>{
return (dispatch) => {
axios.get(`http://localhost:4000/api/blogs/?page=${page}&perPage=3`)
.then(result => {
const responseAPI = result.data
dispatch({type: 'UPDATE_PAGE', payload:
{currentPage: responseAPI.current_page,
totalPage: responseAPI.total_page}}) // this is not working
dispatch({type: 'SET_BLOGS', payload: responseAPI.data}) //just work in here
})
.catch(error => {
console.log('error',error);
})
}}
export default setDataBlog
but if I change the location of the dispatch
import axios from "axios";
const setDataBlog = (page) =>{
return (dispatch) => {
axios.get(`http://localhost:4000/api/blogs/?page=${page}&perPage=3`)
.then(result => {
const responseAPI = result.data
dispatch({type: 'SET_BLOGS', payload: responseAPI.data}) //not working
dispatch({type: 'UPDATE_PAGE', payload:
{currentPage: responseAPI.current_page,
totalPage: responseAPI.total_page}}) // working
})
.catch(error => {
console.log('error',error);
})
}}
export default setDataBlog
I'm trying to use it here
import { useEffect} from "react";
import CardBlog from "../components/CardBlog";
import Pagination from "../components/Pagination";
import {useDispatch, useSelector} from 'react-redux'
import {setDataBlog} from '../redux/action'
const Home = () => {
const {dataBlog, page} = useSelector(state => state.homeReducer);
const dispatch = useDispatch();
//check working or not
console.log('page', page);
useEffect(() => {
dispatch(setDataBlog(1))
}, [dispatch])
return (
<div className="max-w-screen-xl mx-auto px-4 py-16 sm:px-6 lg:px-8">
<div className=" md:grid md:grid-cols-2 lg:grid-cols-3">
{dataBlog?.map(blog => (
<CardBlog key={blog._id} image={`http://localhost:4000/${blog.image}`}
title={blog.title}
body={blog.body}
author={blog.author}
date={blog.createdAt}/>
))}
</div>
<div>
<Pagination/>
</div>
</div>
);
}
export default Home;
thanks, sorry for my bad English, but I hope you understand what I said
I can't tell you why your code is failing, but I'd like to offer some advice. Avoid firing multiple synchronous actions.
Think of an action as representing single thing that has happened: often a user event such as a button click or key press, or in this case a network response.
I recommend combining the two actions above into a single action, e.g.
dispatch({
type: 'BLOG_API_RESPONSE',
payload: {
currentPage: responseAPI.current_page,
totalPages: responseAPI.total_page,
data: responseAPI.data,
},
});
You can hook into BLOG_API_RESPONSE at multiple places in your reducers. Actions to state updates don't have to be one-to-one. One action can produce many state updates.
You'll find your code easier to rationalise and debug when you restrict yourself to firing single synchronous actions.
Try this:
const setDataBlog = (page) => async dispatch => {
try {
const { responseAPI } = await axios.get(`http://localhost:4000/api/blogs/?page=${page}&perPage=3`)
dispatch({type: 'SET_BLOGS', payload: responseAPI.data})
dispatch({type: 'UPDATE_PAGE', payload: {currentPage:responseAPI.current_page, totalPage: responseAPI.total_page}})})
catch(error => {
console.log('error',error);
})
}
Related
Sorry if this is a stupid question, I'm new to properly trying to understand React.
Here's what I'm working with:
import React, { useReducer } from "react";
import axios from "axios";
export const ACTIONS = {
ADD_STANDARD: 'add-to-compare',
REMOVE_STANDARD: 'remove-from-compare'
}
function reducer(standards, action) {
switch(action.type) {
case(ACTIONS.ADD_STANDARD):
return [...standards, addCompare(action.payload.standard)]
case(ACTIONS.REMOVE_STANDARD):
return standards.filter(item => item.id !== action.payload.standard)
default:
return 'Nothing to add'
}
}
function addCompare( standard ) {
return axios
.get("https://the-url.com" + standard)
.then((res) => {
console.log(res.data)
return {
key: res.data.id,
title: res.data.title.rendered
}
})
.catch((err) => console.log(err));
}
export default function EntryStandards() {
const [standards, dispatch] = useReducer(reducer, []);
const addStandards = function(id) {
dispatch({ type: ACTIONS.ADD_STANDARD, payload: {standard: id}})
}
return (
<>
<button onClick={() => addStandards(9603)}>Add 9603!</button>
<button onClick={() => addStandards(9567)}>Add 9567!</button>
<button onClick={() => addStandards(9531)}>Add 9531!</button>
<button onClick={() => addStandards(9519)}>Add 9519!</button>
{standards.map(standard => {
return <p><button onClick={() => dispatch({ type: ACTIONS.REMOVE_STANDARD, payload: { standard: standard.id } })}>X</button> { standard.title } - { standard.version }</p>
})}
</>
)
}
As you can see, I have a button currently which has a hard-coded ID. When clicked, that button triggers a dispatch on a useReducer which performs an API data lookup using Axios against WordPress' Rest API.
If I console log inside the Axios return, I see the data. The return straight after where I create my object however simply returns an empty object into the reducer.
Is the dispatcher not able to use Axios in this way?
Is there a better way to go about this?
As always, your help is appreciated.
Ben
addCompare is an async function. So you can't use it directly in a reducer as a reducer must return a result synchronously.
You may want to change your code to launch you request and then use the reducer to add the data like below:
import React, { useReducer } from "react";
import axios from "axios";
export const ACTIONS = {
ADD_STANDARD: 'add-to-compare',
REMOVE_STANDARD: 'remove-from-compare'
}
function reducer(standards, action) {
switch(action.type) {
case(ACTIONS.ADD_STANDARD):
return [...standards,action.payload.standard]
case(ACTIONS.REMOVE_STANDARD):
return standards.filter(item => item.id !== action.payload.standard)
default:
return 'Nothing to add'
}
}
function addCompare( standard ) {
return axios
.get("https://the-url.com" + standard)
.then((res) => {
console.log(res.data)
return {
key: res.data.id,
title: res.data.title.rendered
}
})
.catch((err) => console.log(err));
}
export default function EntryStandards() {
const [standards, dispatch] = useReducer(reducer, []);
const addStandards = async function(id) {
const standard = await addCompare(id)
dispatch({ type: ACTIONS.ADD_STANDARD, payload: { standard }})
}
return (
<>
<button onClick={() => addStandards(9603)}>Add 9603!</button>
<button onClick={() => addStandards(9567)}>Add 9567!</button>
<button onClick={() => addStandards(9531)}>Add 9531!</button>
<button onClick={() => addStandards(9519)}>Add 9519!</button>
{standards.map(standard => {
return <p><button onClick={() => dispatch({ type: ACTIONS.REMOVE_STANDARD, payload: { standard: standard.id } })}>X</button> { standard.title } - { standard.version }</p>
})}
</>
)
}
Thanks Gabriel for your insight on async. It doesn't feel like a tidy solution, but I've ended up making the reducer store only the ID of the post, and then importing a new component that takes the ID and performs the Axios stage of mapping the data. Thanks for your help with this.
I am using react-paginate (https://www.npmjs.com/package/react-paginate) to make pagination for my app. Everything is fine but I cannot increase the current number of the page. So for this here is my parent component:
import React, { useEffect, useState } from "react";
import Product from "../components/sub-components/Product";
import SimpleBox from "../components/sub-components/SimpleBox";
import BoxWithSearch from "../components/sub-components/BoxWithSearch";
import LoadingBox from "../components/sub-components/LoadingBox";
import MessageBox from "../components/sub-components/MessageBox";
import Cart from "../components/sub-components/Cart";
import { useDispatch, useSelector } from "react-redux";
import { listProducts } from "../actions/productActions";
import ReactPaginate from "react-paginate";
export default function HomeScreen() {
const dispatch = useDispatch();
const productList = useSelector((state) => state.productList);
const { loading, error, products } = productList;
const [currentPage, setCurrentPage] = useState(1);
const [pageCount, setpageCount] = useState(0);
useEffect(() => {
dispatch(listProducts(currentPage));
console.log(currentPage);
}, [dispatch]);
const handlePageClick = (data) => {
setCurrentPage(data.selected + 1);
// scroll to the top
//window.scrollTo(0, 0)
};
return (
<div className="container">
<div className="row">
<div className="col-lg-6 col-md-12 col-sm-12 col-xs-12">
<h2 className="title">Products</h2>
<div className="product-type-filter">
<button>Mug</button>
<button className="clicked">Shirt</button>
</div>
<div className="products">
<div className="row">
<div>
{loading ? (
<LoadingBox></LoadingBox>
) : error ? (
<MessageBox variant="danger">{error}</MessageBox>
) : (
<div className="row center">
{products.map((product) => (
<Product key={product.added} product={product}></Product>
))}
</div>
)}
</div>
</div>
</div>
<ReactPaginate
previousLabel={"Prev"}
nextLabel={"Next"}
pageCount={40}
marginPagesDisplayed={4}
pageRangeDisplayed={1}
onPageChange={handlePageClick}
containerClassName={"pagination justify-content-center"}
pageClassName={"page-item"}
pageLinkClassName={"page-link"}
previousClassName={"page-item"}
previousLinkClassName={"page-link"}
nextClassName={"page-item"}
nextLinkClassName={"page-link"}
breakClassName={"page-item"}
breakLinkClassName={"page-link"}
activeClassName={"active"}
/>
</div>
</div>
</div>
);
}
And my action for fetching the data:
import Axios from 'axios';
import {
PRODUCT_LIST_FAIL,
PRODUCT_LIST_REQUEST,
PRODUCT_LIST_SUCCESS,
} from '../constants/productConstants';
export const listProducts = () => async (dispatch, currentPage) => {
dispatch({
type: PRODUCT_LIST_REQUEST,
});
try {
const { data } = await Axios.get(`http://localhost:3000/items?_page=${currentPage}&_limit=16`);
dispatch({ type: PRODUCT_LIST_SUCCESS, payload: data });
} catch (error) {
dispatch({ type: PRODUCT_LIST_FAIL, payload: error.message });
}
};
But the problem is since the currentPage doesn't change, I cannot go to the other page. Do you have a solution for this?
Thanks...
If you are updating the current page and wanting to fetch new data then you might want to add currentPage to the useEffect dependency array so the next current page of products is fetched/listed.
useEffect(() => {
dispatch(listProducts(currentPage));
console.log(currentPage);
}, [currentPage, dispatch]);
Update
When I write console.log(currentPage) in action, I am getting this:
ƒ getState() { var state = unliftState(liftedStore.getState()); if (state !== undefined) { lastDefinedState = state; } return lastDefinedState; } How can I pass the currentpage number into
action?
In thunks, the second argument is the getState function to be called and get the current redux state. Your listProducts action creator is naming the getState callback currentPage. Also, any arguments being passed to listProducts are being ignored (note the empty arg list in the outer function).
export const listProducts = () => async (dispatch, currentPage) => {
dispatch({
type: PRODUCT_LIST_REQUEST,
});
try {
const { data } = await Axios.get(`http://localhost:3000/items?_page=${currentPage}&_limit=16`);
dispatch({ type: PRODUCT_LIST_SUCCESS, payload: data });
} catch (error) {
dispatch({ type: PRODUCT_LIST_FAIL, payload: error.message });
}
};
listProducts needs to consume the passed currentPage argument in the outer functions, and enclose it in function scope.
export const listProducts = (currentPage) => async (dispatch) => {
dispatch({
type: PRODUCT_LIST_REQUEST,
});
try {
const { data } = await Axios.get(`http://localhost:3000/items?_page=${currentPage}&_limit=16`);
dispatch({ type: PRODUCT_LIST_SUCCESS, payload: data });
} catch (error) {
dispatch({ type: PRODUCT_LIST_FAIL, payload: error.message });
}
};
I am trying to update a form but something is not working as it should. After I click Update, the updated information is logged in the console, but it seems that the Redux side of the state management is not working. I am not getting any errors in the console, but neither my action UPDATE_POST is visible in Redux Dev Tools on Chrome.
Here is the code:
The UpdateForm component:
import { useState , useEffect} from "react";
import { useHistory, useParams } from 'react-router-dom';
import jsonPlaceholder from "../apis/jsonPlaceholder";
import {updatePost} from '../actions'
import { useDispatch } from 'react-redux';
const UpdateForm = () => {
const dispatch = useDispatch()
const history = useHistory();
const { id } = useParams();
const [post, setPost] = useState({});
const [title, setTitle] = useState(post.title);
const [body, setBody] = useState(post.body);
const [author, setAuthor] = useState(post.author);
const fetchPost = async () => {
const response = await jsonPlaceholder.get(`/posts/${id}`)
console.log(response.data)
setPost(response.data)
setTitle(response.data.title)
setBody(response.data.body)
setAuthor(response.data.author)
return response.data
}
useEffect(() => {
fetchPost();
}, [])
const handleUpdate = async (e) => {
e.preventDefault();
const post = { title, body, author }
dispatch(updatePost(post))
console.log('post', post)//updated post is logged in console
history.push('/')
}
console.log("title", title)
return (
<div className="create">
<h2>Update Blog</h2>
<form>
<label>Blog title:</label>
<input
type="text"
required
defaultValue={title}
onChange={(e) => setTitle(e.target.value)}
/>
<label>Blog body:</label>
<textarea
required
defaultValue={body}
onChange={(e) => setBody(e.target.value)}
></textarea>
<label>Author:</label>
<input
type="text"
required
defaultValue={author}
onChange={(e) => setAuthor(e.target.value)}
/>
<button onClick={handleUpdate}>Update</button>
</form>
</div>
);
}
export default UpdateForm;
The action:
export const updatePost = (post) => async dispatch => {
const res = await jsonPlaceholder.put(`posts/update/${post._id}`);
dispatch({
type: UPDATE_POST,
payload: res.data
})
}
And the reducer:
import { ADD_POST, DELETE_POST, UPDATE_POST } from '../actions/types';
const postReducer = (state = [], action) => {
switch (action.type) {
case ADD_POST:
return state.concat([action.data]);
case UPDATE_POST:
return {
...state,
post: action.data
}
case DELETE_POST:
return state.filter((post)=>post.id !== action.id);
default:
return state
}
}
export default postReducer;
Here is the node.js/express server side of the request:
router.put('/update/:id', async (req, res) => {
try {
let post = await Post.findOneAndUpdate(req.params.id, {
title: req.body.title,
body: req.body.body,
author: req.author.body
})
console.log('server', post)
return res.json(post)
} catch (error) {
console.error(error.message);
res.status(500).send('Server Error')
}
})
I am now getting server error (500), and if I remove the line author: req.author.body, I am not getting the error. The code on the front still does not work.
As I see you are directly calling your actions instead of dispatching it
import useDispatch and use it like this
import { useDispatch } from "react-redux";
UpdateForm.js
const UpdateForm = () => {
....
const dispatch = useDispatch();
.....
const handleUpdate = async (e) => {
e.preventDefault();
const post = { title, body, author }
dispatch(updatePost(post)) // dispatch like this
console.log('post', post)//updated post is logged in console
history.push('/')
}
console.log("title", title)
return (
<div className="create">
.......
</div>
);
}
export default UpdateForm;
reducer
instead of action.payload, you're accessing action.data
case UPDATE_POST:
return {
...state,
post: action.payload
}
You need to dispatch the updatePost action, not call it directly. You're missing useDispatch call.
Here's a link to React Redux documentation covering it:
https://react-redux.js.org/api/hooks#usedispatch
Example:
import React from 'react'
import { useDispatch } from 'react-redux'
export const CounterComponent = ({ value }) => {
const dispatch = useDispatch()
return (
<div>
<span>{value}</span>
<button onClick={() => dispatch({ type: 'increment-counter' })}>
Increment counter
</button>
</div>
)
}
[UPDATE]
Just noticed that your updatePost action is an higher order function so once you add the call to useDispatch you'll need to change the call to updatePost from
updatePost(post)
to
updatePost(post)(dispatch)
To be honest I would probably go with a book action creator and move the API call to the component itself. If you're interested in async actions I would suggest looking into react-thunk, it is fairly easy to begin with.
[UPDATE 2]
There seem to be a typo in the express code.
req.author.body
should be
req.body.author
[UPDATE 3]
The post object in the updatePost does not contain the _id field (check your handleUpdate function) thus you're getting the url: "posts/update/undefined".
I need to display an nested array. But I am unable to display the nested list as my redux store is not getting updated. Below is the sample of the structure of the data:
{
email: "fgh#gmail.com"
tId: 2
teacherClasses: null
teacherUserRef: 3
user:
{
admin: null
firstName: "fgh"
id: 3
lastName: "fgh"
}}
I am unable to display anything which is inside user.
below is the code:
Reducer:
import { ACTION_TYPES } from "../actions/teacher";
const initialState = {
list: []
}
export const teacher = (state = initialState, action) => {
switch (action.type) {
case ACTION_TYPES.FETCH_ALL:
return {
...state,
list: [
...action.payload]
}
case ACTION_TYPES.FETCHBYID:
return {
...state,
list: [action.payload]
}
case ACTION_TYPES.CREATE:
return {
...state,
list: [...state.list, action.payload]
}
case ACTION_TYPES.UPDATE:
return {
...state,
list: state.list.map(x => x.id == action.payload.id ? action.payload : x)
}
case ACTION_TYPES.DELETE:
return {
...state,
list: state.list.filter(x => x.id != action.payload)
}
default:
return state
}
}
Component page:
Teacher.js:
const Teacher = ({ ...props }) => {
const [currentId, setCurrentId] = useState(0)
useEffect(() => {
console.log("teacher call")
props.fetchAllTeacher()
console.log(props.teacherList)
}, [currentId])//componentDidMount
return (
<div className="site-layout-background" style={{ padding: 24, textAlign: 'center' }}>
<Space direction="vertical" align="center">
<TableContainer>
<Table>
<TableHead >
<TableRow>
<TableCell>Name</TableCell>
<TableCell>Email</TableCell>
<TableCell></TableCell>
</TableRow>
{
props.teacherList.map((record, index) => {
return (<TableRow key={index} hover>
<TableCell>{record.email}</TableCell>
<TableCell>{record.user.firstName}</TableCell>
<TableCell>
<ButtonGroup variant="text">
<Button icon={<DeleteOutlined />} onClick={() => onDelete(record.user.id)}></Button>
</ButtonGroup>
</TableCell>
</TableRow>)
})}
</TableHead>
<TableBody>
</TableBody>
</Table>
</TableContainer>
</Space>
</div>
);
}
const mapStateToProps = state => ({
teacherList: state.teacher.list,
userList: state.user.list
})
const mapActionToProps = {
fetchAllTeacher: actions.fetchAll,
deleteUser: actions1.Delete
}
export default connect(mapStateToProps, mapActionToProps)(Teacher);
Action creator:
import api from "./api";
export const ACTION_TYPES = {
CREATE: 'CREATE',
UPDATE: 'UPDATE',
DELETE: 'DELETE',
FETCH_ALL: 'FETCH_ALL',
FETCHBYID: 'FETCHBYID'
}
export const fetchAll = () => dispatch => {
api.teacher().fetchAll()
.then(response => {
dispatch({
type: ACTION_TYPES.FETCH_ALL,
payload: response.data
})
})
.catch(err => console.log(err))
}
export const fetchById = (id) => dispatch => {
api.teacher().fetchById(id)
.then(response => {
dispatch({
type: ACTION_TYPES.FETCHBYID,
payload: response.data
})
})
.catch(err => console.log(err))
}
export const create = (data, onSuccess) => dispatch => {
api.teacher().create(data)
.then(res => {
dispatch({
type: ACTION_TYPES.CREATE,
payload: res.data
})
onSuccess()
})
.catch(err => console.log(err))
}
export const update = (id, data, onSuccess) => dispatch => {
api.teacher().update(id, data)
.then(res => {
dispatch({
type: ACTION_TYPES.UPDATE,
payload: { id, ...data }
})
onSuccess()
})
.catch(err => console.log(err))
}
export const Delete = (id, onSuccess) => dispatch => {
api.teacher().delete(id)
.then(res => {
dispatch({
type: ACTION_TYPES.DELETE,
payload: id
})
onSuccess()
})
.catch(err => console.log(err))
}
I am getting an error saying firstName is undefined.
Please help.
Recommendation
Since you are using functional component, you should use react-redux hooks like useSelector, useDispatch.
import { useSelector, useDispatch } from "react-redux"
...
teacherList = useSelect(state => state.teacher.list)
userList = useSelect(state => state.user.list)
const dispatch = useDispatch()
...
{
dispatch(actions.fetchAll(...))
dispatch(actions1.Delete(...))
}
Problem
First, you don't need to set currentId as a dependency of useEffect.
When dependency is an empty list, the callback will only be fired once, similar to componentDidMount.
Second, fetchAllTeacher is an asynchronous action which means you need to wait until all teachers are fetched successfully.
So you need to add a lazy loading feature.
The reason that your redux store is not getting updated is because you must dispatch the actions. The correct signature for mapDispatchToProps is:
const mapDispatchToProps = (dispatch) => {
fetchAllTeacher: () => dispatch(actions.fetchAll()),
deleteUser: (id) => dispatch(actions.Delete(id)),
}
export default connect(mapStateToProps, mapDispatchToProps)(Teacher);
BUT the there is a better way. You are actually mixing two paradigms, and while the above will work, you should use redux hooks, since you have created a functional component and you are already using the useEffect hook.
It could work like this:
import { useSelector, useDispatch } from "react-redux"
const Teacher = ({ ...props }) => {
const dispatch = useDispatch();
useEffect(() => {
console.log("teacher call")
const teachers = props.fetchAllTeacher();
// dispatch the action that will add the list to the redux state
dispatch(actions.fetchAll(teachers));
}, [currentId]);
// fetch the teacher list from redux store
const teacherList = useSelector(state => state.teacher.list);
return (...);
}
Consider moving the selector definition state => state.teacher.list to its own module so that you can reuse it in other components and update it in one place if the structure of your store changes.
It looks like no actions were getting dispatched in your code, so the problem was not due to nesting of the data. You can have nested data in your state without a problem.
I am developing a component library and want to use the host project's store. I want the component library could access the host project's store. so I use the hook useSelector, useDispath,I found that I could use normal dispatch, but not async dispatch.
import React from "react";
import { useSelector, useDispatch } from "react-redux";
export default function AccountInfo({ onAccountClick }) {
const name = useSelector((state) => state.account.name);
const balance = useSelector((state) => state.account.balance);
const dispatch = useDispatch();
const fetchImage = (url) => async () => {
try {
const res = await fetch(url, {
method: "GET",
});
const address = await res.json();
dispatch({ type: "GET_ACCOUNT_IMAGE", address });
} catch (err) {
console.log("error");
}
};
return (
<>
<ul>
<li>name: {name}</li>
<li>balance: {balance}</li>
</ul>
<button className="button" onClick={() => fetchImage("https://aws.random.cat/meow")}>
Click
</button>
</>
);
}
If I change onClick to :
<button className="button" onClick={() => dispatch({ type: "GET_CONSOLE" })}>
It can do the work. But I want it an async function, which didn't work.
How can I solve this?