I am new to React and Redux. Learning now about hooks and got really confused.
Doing a tutorial app (the teacher is using classes) which should fetch some API data from jsonplaceholder (async) and afterwards use it with redux. For now, I fail to display the fetched data on my screen.
Also at the very bottom are two of my additional questions.
My code (that is not working):
ERROR:
TypeError: posts.map is not a function
PostList.js
import React, { useEffect, useState } from "react";
import { fetchPosts } from "../actions";
import { useSelector } from "react-redux";
const PostList = () => {
const [ posts, getPosts ] = useState("");
// posts = useSelector((state) => state.posts);
// const dispatch = useDispatch();
useEffect(() => {
setPosts(fetchPosts());
}, []);
return (
<div className="ui relaxed divided list">
<ul>{posts.map((post) => <li key={post.id}>{post.title}</li>)}</ul>
</div>
);
};
export default PostList;
action/index.js
import jsonPlaceholder from "../apis/jsonPlaceholder";
export const fetchPosts = () => async (dispatch) => {
const response = await jsonPlaceholder.get("/posts");
dispatch({ type: "FETCH_POSTS", payload: response.data });
};
apis/jsonPlaceholder.js
import jsonPlaceholder from "../apis/jsonPlaceholder";
export const fetchPosts = () => async (dispatch) => {
const response = await jsonPlaceholder.get("/posts");
dispatch({ type: "FETCH_POSTS", payload: response.data });
};
reducers/postsReducer.js
export default (state = [], action) => {
switch (action.type) {
case "FETCH_POSTS":
return action.payload;
default:
return state;
}
};
I got it to work (to show the posts on my screen with the following) with this:
components/PostList.js
import React, { useEffect, useState } from "react";
import { fetchPosts } from "../actions";
import axios from "axios";
const PostList = () => {
const [ posts, setPosts ] = useState([]);
useEffect(() => {
axios
.get("https://jsonplaceholder.typicode.com/posts")
.then((response) => {
console.log(response);
setPosts(response.data);
})
.catch((err) => {
console.log(err);
});
}, []);
return (
<div className="ui relaxed divided list">
<ul>{posts.map((post) => <li key={post.id}>{post.title}</li>)}</ul>
</div>
);
};
export default PostList;
1) But I do not use any async nor await in useEffect. Is this correct?
2) Should I use a middleware (like thunk) when I use useEffect?
3) What is with redux hooks like useSelector and useDispatch, where should I use them or should I be using either react hooks or either redux hooks?
Working code (only changed the PostList.js file):
import React, { useEffect } from "react";
import { fetchPosts } from "../actions";
import { useSelector, useDispatch } from "react-redux";
const PostList = () => {
// const [ posts, setPosts ] = useState([]);
const posts = useSelector((state) => state.posts);
const dispatch = useDispatch();
useEffect(
() => {
dispatch(fetchPosts());
},
[ dispatch ]
);
return (
<div className="ui relaxed divided list">
<ul>{posts.map((post) => <li key={post.id}>{post.title}</li>)}</ul>
</div>
);
};
export default PostList;
you are using .then for waiting for the call to end, as much as async tells the code to wait
you need to use redux-thunk if you want to run this action as redux action (because the usage of async behavior, .then either async), there is no relation between useEffect which is react effect to redux-thunk that belongs to redux part of your project
you need useDispatch to dispatch function from UI
const dispatch = useDispatch()
useEffect(() => {
dispatch(fetchPosts());
}, []);
and useSelector for subscribing a redux prop to the component (as mapStateToProps)
const posts = useSelector((state) => state.posts);
if you use them both, const [ posts, getPosts ] = useState([]); unneeded
Related
I have an existing context for products. Where initially I used some mock data as shown below STORE_DATA to render the components. Now I need to replace that mock data and connect to a Node.js api which is available on my local port (created the api I after I created the react-app).
import React, { createContext, useState } from 'react';
import STORE_DATA from '../shop';
export const ProductsContext = createContext();
const ProductsContextProvider = ({ children }) => {
const [products] = useState(STORE_DATA);
return (
<ProductsContext.Provider value={{ products }}>
{
children
}
</ProductsContext.Provider>
);
}
export default ProductsContextProvider;
Just created a helper.js file witht he following to fetch the data:
import {useEffect} from "react";
const fetchData = () => {
return fetch("https://localhost:8081/products") <<tested on postman and works fine.
.then((response) => response.json())
.then((data) => console.log('Fetching Data:',data));
}
How to replace the mock data on the context file and use this fetchData() using useEffect within the context? What code should change?
Tried the following, but didn't work, can't even print the console.log:
import React, { createContext, useState, useEffect } from 'react';
import { fetchData } from '../helpers';
export const ProductsContext = createContext();
const ProductsContextProvider = ({ children }) => {
const [products, setProducts] = useState(null);
useEffect(() => {
setProducts(fetchData());
}, []);
return (
<ProductsContext.Provider value={{ products }}>
{
children
}
</ProductsContext.Provider>
);
}
export default ProductsContextProvider;
The issue was that it was returning the following error (explained):
net::ERR_SSL_PROTOCOL_ERROR (on chrome)
Solution: Use http:// instead of https:// in the URL's in the following code:
const fetchData = () => {
return fetch("http://localhost:8081/products")
.then((response) => response.json())
.then((data) => console.log('Fetching Data:',data));
}
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".
The useEffect below renders, fetches data, and displays it once (using an empty array for 2nd parameter in useEffect).
I need it to rerun useEffect everytime the user changes data to the database (when user uses axios.post).
What i've tried
using [tickets], but that just causes the useEffect to run infinitly
also using [tickets.length] and [tickets, setTickets]
trying to use props as parameter but didnt find anything useful
import React, { useState, createContext, useEffect } from "react";
import axios from "axios";
export const TicketContext = createContext();
export const TicketProvider = (props) => {
console.log(props);
const [tickets, setTickets] = useState([]);
useEffect(() => {
getTickets();
console.log("1", { tickets });
}, []);
const getTickets = async () => {
const response = await axios.get("http://localhost:4000/tickets/");
setTickets(response.data);
};
return <TicketContext.Provider value={[tickets, setTickets]}>{props.children}
</TicketContext.Provider>;
};
import React from "react";
import { useState, useEffect, useContext } from "react";
import Ticket from "../Ticket";
import { TicketContext } from "../contexts/TicketContext";
import AddBacklog from "../addData/AddBacklog";
const TicketDisplay = (props) => {
const [tickets, setTickets] = useContext(TicketContext);
return (
<div className="display">
<p>Antony Blyakher</p>
<p>Number of Tickets: {tickets.length}</p>
<div className="backlog">
<h1>Backlog</h1>
{tickets.map((currentTicket, i) => (
<div className="ticketBlock">
<Ticket ticket={currentTicket} key={i} />
</div>
))}
</div>
</div>
);
const AddBacklog = (props) => {
const [tickets, setTickets] = useState("");
...
axios.post("http://localhost:4000/tickets/add", newTicket).then((res) => console.log(res.data));
setTickets((currentTickets) => [...currentTickets, { name: name, status: "backlog", id: uuid() }]);
};
You'll need to watch for tickets and return if it has data to not cause infinite loop:
useEffect(() => {
if (tickets.length) return // so, we call just once
getTickets();
console.log("1", { tickets });
}, [tickets]);
const fetchData = () => {
axios.get("http://localhost:7000/api/getData/").then((response) => {
console.log(response.data);
if (response.data.success) {
SetIsLoading(false);
}
setDataSource(response.data.data);
});
};
useEffect(() => {
fetchData();
if (fetchData.length) fetchData();
}, [fetchData]);
by this you can fetch the data in real-time as any change in data occurs.
I have multiple modals and the conditions for showing and hiding them are stored in a Redux store. To avoid typos I export the actions like this...
//reduxStuff.js
export const modalNameHide = () => {
return { type: "MODALNAMEHIDE" };
};
export const modalNameShow = () => {
return { type: "MODALNAMESHOW" };
};
Then when I need the functionality for hiding a modal I do this...
//HideModalButton.js
import { modalNameHide } from "reduxStuff";
import { useDispatch } from "react-redux";
const HideModalNameButton = () => {
const dispatch = useDispatch();
return <button onClick = {() => { dispatch(modalNameHide()) }>Close Modal<button>
}
As you can see for every component that needs to update the store, 2 imports must be done, one for useDispatch and one for the needed action (modalNameHide in this example).
So my question boils down to this: Can I import useDispatch in reduxStuff.js and export useDispatch and modalNameHide together?
Something like this...
//reduxStuff.js
import { useDispatch } from "react-redux";
export const modalNameHide = () => {
let dispatch = useDispatch();
dispatch({ type: "MODALNAMEHIDE" });
};
export const modalNameShow = () => {
let dispatch = useDispatch();
dispatch({ type: "MODALNAMESHOW" });
};
Now when I need to close a modal I would just do one import like this...
//HideModalButton.js
import { modalNameHide } from "reduxStuff";
const HideModalNameButton = () => {
return <button onClick = {() => { modalNameHide() }>Close Modal<button>
}
No, this is not valid.
In your first case, you're doing useDispatch() during render, and then you call dispatch(modalNameHide()) during onClick. This is valid and correct usage of useDispatch.
In your theoretical second case, you're doing modalNameHide() during onClick, and this means that useDispatch() will also be used during onClick instead of render (which is incorrect usage of useDispatch).
useDispatch is a react hook, and you're breaking the Rules of Hooks. Read more here: https://reactjs.org/docs/hooks-rules.html
As grumd said, I was breaking the rules of hooks by doing what I did. However I just figured out what the solution is. All I had to do was make a custom hook.
//reduxStuff.js
import { useDispatch } from "react-redux";
export const useModalNameHide = () => {
const dispatch = useDispatch();
return () => {
dispatch({ type: "MODALNAMEHIDE" });
};
};
export const useModalNameShow = () => {
const dispatch = useDispatch();
return () => {
dispatch({ type: "MODALNAMEHIDE" });
};
};
.
//HideModalButton.js
import { useModalNameHide } from "reduxStuff";
const HideModalNameButton = () => {
const modalNameHide = useModalNameHide()
return <button onClick = {() => { modalNameHide() }>Close Modal<button>
}