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?
Related
I'm facing difficulty displaying data in React - Here is my code:
import Axios from 'axios';
import { useNavigate } from 'react-router';
export default function ProductCatalog() {
let navigate = useNavigate();
function addProduct() {
navigate('/adding')
}
const [products, setProducts] = useState([{}])
useEffect(() => {
const axiosProd = async () => {
const response = await Axios('http://localhost:3001/getProducts');
setProducts(response.data)
};
axiosProd();
}, []);
const useProducts = products.map((product)=>{
return <div>
<h1>{product.name}</h1>
</div>
})
return(
<>
<button className = "button" onClick={addProduct}>Add New Product</button>
<br></br>
{useProducts}
</>
)
}
I know data is coming in as JSON Objects as when i follow the link of http://localhost:3001/getProducts, I see my data. What am i doing wrong?
You should make a function then outside of the function call the use effect.
To do a get request using axios use axios.get(api)
For example:
// Get All Shoes
const getShoes = () => {
axios.get('/shoes')
.then(res => setShoes(res.data))
.catch(err => console.log(err));
}
Then
useEffect(() => {
getShoes();
}, [])
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 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);
})
}
I have component that use thunk action.
And inside the component, I have an asynchronous execution that, after processing, returns the username to the markup, how to lock the result of such an execution
import React, { useEffect } from 'react'
import { useDispatch, useSelector } from "react-redux";
import setTestData from "../redux/asyncServices/testService";
function TestPage() {
const dispatch = useDispatch()
const user = useSelector((state) => state.testReducer.user)
const loading = useSelector((state) => state.testReducer.loading)
useEffect(() => {
dispatch(setTestData())
}, [])
return (
<div className='users_wrapper'>
<div className='container'>
<div className='users_content'>
<div className='title'>
<h1>Test</h1>
</div>
{
!loading ? <h1>{user.name}</h1> : null
}
</div>
</div>
</div>
)
}
export default TestPage
Async action
import { createAsyncThunk } from '#reduxjs/toolkit'
import db from '../../indexedDB/db'
import '../../indexedDB/db.timesheetHooks'
const setTestData = createAsyncThunk(
'setTestData',
async () => {
const user = await db.loggedUser.orderBy('id').last()
return {user}
},
)
And code where I try to mock function result, but something went wrong.I understand how mock simple function in jest, but how mock in this case
jest.mock('../redux/asyncServices/testService', () => {
return {
setTestData: () => ({type: "setTestData/fulfilled", payload: {
user: {name: 'Loaded user name'}
}}),
};
});
describe('Timesheet Menu page tests', () => {
beforeEach(async () => {
matchMedia = new MatchMediaMock()
})
afterEach(() => {
matchMedia.clear()
})
test('Component renders correctly', async () => {
const testFn = require('../pages/TestPage');
jest.spyOn(testFn, 'setTestData').mockReturnValue('c');
await waitFor(() => {
renderWithRedux(<TestPage/>, {initialState})
})
expect(screen.getByText('Test')).toBeInTheDocument()
expect(screen.getByText('Loaded user name')).toBeInTheDocument()
})
})
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".