I'm trying to migrate to redux-thunk I have few issues here.
Previously, I had fetch response data which is saved in state, and with that state I used filter to save other states in the app.
loadMeetingRoomData = async () => {
try {
const getMeetingRoomData = await fetch(URL, { headers: myHeaders });
const responseJson = await getMeetingRoomData.json();
if (responseJson.length === 0) {
this.setState({
meetingRoomStatus: false,
})
} else {
this.setState({
meetingRoomData: responseJson,
});
const inProgressMeeting = responseJson.filter(obj => {
return obj.Status === INPROGRESS_CODE
});
this.setState({
meetingRoomInProgressCount: inProgressMeeting.length,
});
const upcomingMeeting = responseJson.filter(obj => {
return obj.Status === UPCOMING_CODE_MORE_THAN_30_MIN || obj.Status === UPCOMING_CODE_LESS_THAN_30_MIN;
});
this.setState({
meetingRoomUpcomingCount: upcomingMeeting.length,
})
const finishedMeeting = responseJson.filter(obj => {
return obj.Status === FINISHED_CODE
})
this.setState({
meetingRoomFinishedCount: finishedMeeting.length,
})
}
}
catch (err) {
console.log(new Error(err));
}
}
And below is the code, My question is where do I filter the reponseJson to
update the new three states in the previous code ( meetingRoomInProgressCount, meetingRoomUpComingCount, meetingRoomFinished)
Do I have to dispatch it in the component file using mapDispatchToProps or
use middleware to update the store , or filter inside the reducer?
#Action Creator
export const fetchMeetingRoom = () => {
return function(dispatch){
return fetch(URL, { headers: myHeaders })
.then(response => response.json())
.then(json =>
dispatch({ type: 'FETCH_MEETINGROOM_SUCCESS' ,payload : json})
)
}
}
export default function reducer( state = initialState , action){
switch(action.type) {
case FETCH_MEETINGROOM_SUCCESS :
let value = action.payload;
if(value.length === 0){ return {...state,meetingRoomStatus :false,}}
else{
return {...state,
isLoading : false,
meetingRoomData : action.payload}
}
default :
return state;
}
}
I use this structure for my projects
actions:
import { TYPE_VARS } from "./type";
export const func_name = () => (dispatch) => {
return fetch(URL, {
method: "GET",
headers: {
"Content-Type": "application/json",
}
})
.then((res) => res.json())
.then((data) => {
dispatch({
type: TYPE_VARS,
payload: data,
});
return data;
})
.catch((err) => console.log(err.message));
};
reducer:
import { TYPE_VARS } from "../actions/type";
const initialState = {
my_state: {},
};
export default function (state = initialState, action) {
const { type, payload } = action;
switch (type) {
case TYPE_VARS:
return {
...state,
my_state: payload,
};
default:
return state;
}
}
component:
// call action where do you need
func_name()
const mapStateToProps = state => ({
my_state: state.reducerFileName.my_state
})
export default connect(mapStateToProps, { func_name })(ComponentName)
Related
I'm successfully updating my plant object to my cluster, but it takes a page reload in order for me to get that updated data. I'm assuming that I may need a useEffect to call my fetch again but I'm unsure how I would do that after my PATCH request.
Does anyone have any suggestions to how I would fetch my updated data after I've updated.
Context
import { createContext, useReducer } from 'react'
export const PlantsContext = createContext()
export const plantsReducer = (state, action) => {
switch(action.type) {
case 'SET_PLANTS':
return {
plants: action.payload
}
case 'CREATE_PLANT':
return {
plants: [action.payload, ...state.plants]
}
case 'DELETE_PLANT':
return {
plants: state.plants.filter((p) => p._id !== action.payload._id)
}
case 'UPDATE_PLANT':
return {
plants: state.plants.map((p) => p._id === action.payload._id ? action.payload : p)
}
default:
return state
}
}
export const PlantsContextProvider = ({ children }) => {
const [state, dispatch] = useReducer(plantsReducer, {
plants: null
})
return (
<PlantsContext.Provider value={{...state, dispatch}}>
{ children }
</PlantsContext.Provider>
)
}
My 'update' function inside PlantDetails component, setting a new water date
const updatePlant = async (e) => {
e.preventDefault()
plant.nextWaterDate = newWaterDate
const response = await fetch("api/plants/" + plant._id, {
method: "PATCH",
body: JSON.stringify(plant),
headers: {
'Content-Type': 'application/json'
}
})
const json = await response.json()
if(response.ok) {
dispatch({ type: "UPDATE_PLANT", payload: json })
}
}
My Home component where that update should render through after PATCH request
const Home = () => {
const { plants, dispatch } = usePlantsContext();
useEffect(() => {
const fetchPlants = async () => {
console.log("called");
// ONLY FOR DEVELOPMENT!
const response = await fetch("/api/plants");
const json = await response.json();
if (response.ok) {
dispatch({ type: "SET_PLANTS", payload: json });
}
};
fetchPlants();
}, [dispatch]);
return (
<div className="home">
<div className="plants">
{plants &&
plants.map((plant) => <PlantDetails key={plant._id} plant={plant} />)}
</div>
<PlantForm />
</div>
);
};
export default Home;
usePlantContext
import { PlantsContext } from "../context/PlantContext";
import { useContext } from "react";
export const usePlantsContext = () => {
const context = useContext(PlantsContext)
if(!context) {
throw Error('usePlantsContext must be used inside an PlantsContext Provider')
}
return context
}
Complete PlantsDetails Component
import { usePlantsContext } from "../hooks/usePlantsContext";
import formatDistanceToNow from "date-fns/formatDistanceToNow";
import { useState } from "react";
import CalendarComponent from "./CalendarComponent";
const PlantDetails = ({ plant }) => {
const [watered, setWatered] = useState(false)
const [newWaterDate, setNewWaterDate] = useState("")
const { dispatch } = usePlantsContext();
const handleClick = async () => {
const response = await fetch("/api/plants/" + plant._id, {
method: "DELETE",
});
const json = await response.json();
if (response.ok) {
dispatch({ type: "DELETE_PLANT", payload: json });
}
};
const updatePlant = async (e) => {
e.preventDefault()
plant.nextWaterDate = newWaterDate
const response = await fetch("api/plants/" + plant._id, {
method: "PATCH",
body: JSON.stringify(plant),
headers: {
'Content-Type': 'application/json'
}
})
const json = await response.json()
if(response.ok) {
dispatch({ type: "UPDATE_PLANT", payload: json })
}
// setWatered(false)
}
return (
<div className="plant-details">
<h4>{plant.plantName}</h4>
<p>{plant.quickInfo}</p>
<p>
{formatDistanceToNow(new Date(plant.createdAt), { addSuffix: true })}
</p>
<span onClick={handleClick}>delete</span>
<div>
<p>next water date: {plant.nextWaterDate}</p>
{/* <input type="checkbox" id="toWater" onChange={() => setWatered(true)}/> */}
<label value={watered} for="toWater">watered</label>
<CalendarComponent setNextWaterDate={setNewWaterDate}/>
</div>
<button onClick={updatePlant}>update</button>
</div>
);
};
export default PlantDetails;
Plant Controller
const updatePlant = async (req, res) => {
const { id } = req.params
if(!mongoose.Types.ObjectId.isValid(id)) {
return res.status(404).json({ error: "No plant" })
}
const plant = await Plant.findByIdAndUpdate({ _id: id }, {
...req.body
})
if (!plant) {
return res.status(400).json({ error: "No plant" })
}
res.status(200).json(plant)
}
Thank you for looking at my question, would appreciate any suggestion.
I am using react and redux to make a social media app. I'm storing all the data regarding posts in the firebase realtime-database. But when I fetch it I'm unable to assign firebase name property as an id to each and every post.
This is the action responsible for fetching data from firebase.
export const FetchPostStart = () => {
return {
type: actionTypes.Fetch_Post_Start
};
};
export const FetchPostSuccess = (fetchedData) => {
return {
type: actionTypes.Fetch_Post_Success,
payload: fetchedData
}
}
export const FetchPostError = (error) => {
return {
type: actionTypes.Fetch_Post_Error,
error: error
}
}
export const FetchPost = () => {
return dispatch => {
dispatch(FetchPostStart());
axios.get('/Data.json')
.then(response => {
const fetchedData = [];
for(let key in response.data){
fetchedData.push({
...response.data[key],
id: response.data.name
});
}
dispatch(FetchPostSuccess(fetchedData));
})
.catch(error => {
dispatch(FetchPostError(error));
});
}
}
This is the reducer function
case actionTypes.Fetch_Post_Start:
return {
...state,
loading:true
}
case actionTypes.Fetch_Post_Error:
return {
...state,
loading:false
}
case actionTypes.Fetch_Post_Success:
return {
...state,
loading: false,
Data: action.payload
}
The id remains undefined.
EDIT
This is how I'm trying to store id for new posts.
These are the action functions for adding a new post and to delete a post. The firebase name property is getting set as id here. But when I try to delete a post it passes a null value instead of the id.
export const NewPostSuccess = (id, postData) => {
return {
type: actionTypes.New_Post_Success,
payload: {
data: postData,
index: id
}
}
}
export const NewPostError = (error) => {
return {
type: actionTypes.New_Post_Error,
error: error
}
}
export const NewPost = (postData) => {
return (dispatch) => {
axios.post('/Data.json', postData)
.then(response => {
dispatch(NewPostSuccess(response.data.name, postData));
})
.catch(error => {
dispatch(NewPostError(error));
})
}
}
export const DeletePostSuccess = (id) => {
return {
type: actionTypes.Delete_Post_Success,
ID: id
}
}
export const DeletePost = (ID) => {
return (dispatch) => {
axios.delete('/Data/'+ ID + '.json')
.then(response => {
console.log(response.data);
dispatch(DeletePostSuccess(ID));
})
.catch(error => {
dispatch(DeletePostError(error));
})
}
}
THis is the reducer
case actionTypes.New_Post_Success:
const {Comment, ImageUrl, Date, User} = action.payload.data;
const id = action.payload.index;
console.log(id+"Reducer function")
return {
...state,
loading: false,
Data: [
...state.Data,
{Comment, ImageUrl, Date, User},
id
],
}
case actionTypes.Delete_Post_Success:
return {
...state,
loading: false,
}
.then(response => {
const fetchedData = [];
for(let key in response.data){
fetchedData.push({
...response.data[key],
id: response.data[key].name //made a small change here
});
}
dispatch(FetchPostSuccess(fetchedData));
})
I want to add a data to the restfull api by action.
But I get this error.
export const GlobalContext = createContext();
const GlobalProvider = ({ children }) => {
const [userData, setUserData] = useState([]);
const [meetings, setMeetings] = useState([]);
useEffect(() => {
fetch('http://localhost:4000/users')
.then(res => res.json())
.then(data => {
setUserData(data);
dispatch({
type: 'CREATE_MEETING',
paylaod: data
})
});
fetch('http://localhost:4000/meeting')
.then(res => res.json())
.then(data => setMeetings(data));
}, []);
const [state, dispatch] = useReducer(AppReducer, meetings);
//Actions
const updateProfile = (id) => {
dispatch({
type: 'UPDATE_PROFILE',
payload: id
})
};
const createMeeting = (meeting) => {
dispatch({
type: 'CREATE_MEETING',
paylaod: meeting
})
};
return (
<GlobalContext.Provider value={{
meeting: meetings, userData, createMeeting
}}>
<MuiPickersUtilsProvider utils={DateFnsUtils}>
{children}
</MuiPickersUtilsProvider>
</GlobalContext.Provider>
)
}
export default GlobalProvider
reducer
const reducer = (state, action) => {
switch (action.type) {
case 'CREATE_MEETING':
return {
meeting: [action.payload, ...state.meetings]
}
default:
return state;
}
}
export default reducer;
How can I add data to the api with fetch?
case 'CREATE_MEETING':
console.log(state)
return [...state,
fetch('http://localhost:4000/meeting', {
method: 'POST',
headers: { "Content-type": "Application/json" },
body: JSON.stringify(state)
})
]
could you help me please?
As explained in Spreading undefined in array vs object you get a TypeError when trying to spread undefined.
Either wrap your setMettings in a conditional:
data => {
if (data) {
setMeetings(data)
}
}
Or provide a default for state.mettings in your reducer:
const reducer = (state, action) => {
switch (action.type) {
case 'CREATE_MEETING':
return { meeting: [action.payload, ...(state.meetings || [])] }
}
}
Actions
import { FETCH_BLOG, FETCH_BLOG_ERROR, FETCH_BLOG_LOADING } from "../constants/blogActionTypes"
const initialState = {
blogs: [],
error: '',
loading: false,
allBlogs: []
}
// eslint-disable-next-line import/no-anonymous-default-export
export default (blogs = initialState, action) => {
switch (action.type) {
case FETCH_BLOG_LOADING:
return {
blogs: [...blogs.blogs],
loading: true,
error: ''
};
case FETCH_BLOG_ERROR:
return {
blogs: [...blogs.blogs],
loading: false,
error: action.payload
};
case FETCH_BLOG:
return {
blogs: [...action.payload, ...blogs.blogs],
loading: false,
error: ''
};
default: return blogs;
}
}
Reducers
export const fetchBlogs = (data) => async (dispatch) =>{
dispatch({ type: FETCH_BLOG_LOADING, payload: true })
fetch('http://localhost:5000/blog?show=' + data, {
method: 'GET',
headers: {
authorization: userData.token
}
})
.then(res => res.json())
.then(data => {
if (data.message) {
dispatch(fetchBlogsError(data.message))
} else {
dispatch({ type: FETCH_BLOG, payload: data })
}
})
}
React
const [fetchData, setFetchData] = useState(0);
const showData = () => {
setFetchData(fetchData + 10)
}
const dispatch = useDispatch();
const { loading, error, blogs, } = useSelector(state => state.blogs)
const getData = useCallback( () => {
dispatch(fetchBlogs(fetchData))
}, [fetchData])
useEffect(() => {
getData()
}, [getData])
On the first render, I fetch 10 items.after clicking on load more I fetch another 10 data from database. On the blog component it's fine but after go back to the home page and get back to the blog page; the blog items duplicates. How to fix this duplicate issue>
There are two issues here which are inter-related, you possibly don't need to address #2 depending on how you address #1.
You should add a condition to your thunk action so that you don't fetch a page that you have previously fetched.
You should separate your blog items by page so that you aren't always appending the newest items at the end of the array if you fetch page 1 twice.
Sidenote: [...blogs.blogs] is unnecessary because there is reason to clone properties which you aren't changing.
I'm confused by your API calls. It looks like /blog?show=20 is getting posts 21-30 but I would think based on the name show that it would be posts 1-20.
Using position indexes:
import { createAsyncThunk, createReducer } from "#reduxjs/toolkit";
export const fetchBlogs = createAsyncThunk(
"blogs/fetchBlog",
async (startIndex, { getState, rejectWithValue }) => {
const res = await fetch("http://localhost:5000/blog?show=" + startIndex, {
method: "GET",
headers: {
// where does userData come from ??
authorization: userData.token
}
});
const data = await res.json();
if (data.message) {
rejectWithValue(data.message);
} else {
return data;
}
},
{
condition: (startIndex, { getState }) => {
const { blogs } = getState();
// cancel if loading of if first post on paage is loaded
if (blogs.loading || blogs.blogs[startIndex]) {
return false;
}
}
}
);
const initialState = {
blogs: [],
error: "",
loading: false
};
export default createReducer(initialState, (builder) =>
builder
.addCase(fetchBlogs.pending, (state) => {
state.loading = true;
state.error = "";
})
.addCase(fetchBlogs.rejected, (state, action) => {
state.loading = false;
state.error = action.payload ?? action.error;
})
.addCase(fetchBlogs.fulfilled, (state, action) => {
const startIndex = action.meta.arg;
const newBlogs = action.payload;
// insert in the array at the correct position
state.blogs.splice(startIndex, newBlogs.length, newBlogs);
})
);
Using separated pages:
import { createAsyncThunk, createReducer, createSelector } from "#reduxjs/toolkit";
export const fetchBlogs = createAsyncThunk(
"blogs/fetchBlog",
async (pageNumber, { getState, rejectWithValue }) => {
const startIndex = 10 * (pageNumber - 1);
const res = await fetch("http://localhost:5000/blog?show=" + startIndex, {
method: "GET",
headers: {
// where does userData come from ??
authorization: userData.token
}
});
const data = await res.json();
if (data.message) {
rejectWithValue(data.message);
} else {
return data;
}
},
{
condition: (pageNumber, { getState }) => {
const { blogs } = getState();
// cancel if loading of if there is a property for this page
if (blogs.loading || blogs.blogs[pageNumber]) {
return false;
}
}
}
);
const initialState = {
//arrays keyed by page number
blogs: {},
error: "",
loading: false
};
export default createReducer(initialState, (builder) =>
builder
.addCase(fetchBlogs.pending, (state) => {
state.loading = true;
state.error = "";
})
.addCase(fetchBlogs.rejected, (state, action) => {
state.loading = false;
state.error = action.payload ?? action.error;
})
.addCase(fetchBlogs.fulfilled, (state, action) => {
const pageNumber = action.meta.arg;
state.blogs[pageNumber] = action.payload;
})
);
// want to flatten the blogs array when selecting
// create a memoized selector
export const selectBlogs = createSelector(
state => state.blogs,
(blogsState) => ({
...blogsState,
blogs: Object.values(blogsState.blogs).flat(1)
})
)
With component:
export default () => {
const [pageNumber, setPageNumber] = useState(1);
const showNext = () => {
setPageNumber((page) => page + 1);
};
const dispatch = useDispatch();
const { loading, error, blogs } = useSelector(selectBlogs);
useEffect(() => {
dispatch(fetchBlogs(pageNumber));
}, [dispatch, pageNumber]);
consider the following code :
const onSubmit = (data) => {
dispatch(Actions.updatedUser(data))
navigation.navigate('xxxx')
}
When I call Submit function we want to wait until finishing dispatch then navigate , Here How can I do that ?
This is my action :
export const updatedUser = (model) => {
return dispatch => {
api
.patch("/xxx")
.then(response => { return response.data['data'] })
.then(result => {
dispatch({ type: Actions.AUTH_UPDATE_USER, payload: result })
})
.catch(error => { })
}
}
my reducer :
const initState = {
userInfo: undefined
}
export default (state = initState, action) => {
switch (action.type) {
case Actions.AUTH_UPDATE_USER:
return { ...state, userInfo: action.payload }
default:
return state;
}
}
Here's what you can do: from your action, you can return a promise which resolves only when the dispatch is completed. Something like this:
export const updatedUser = (model) => {
return dispatch => {
return new Promise((resolve, reject) => {
api
.patch("/xxx")
.then(response => { return response.data['data'] })
.then(result => {
dispatch({ type: Actions.AUTH_UPDATE_USER, payload: result })
resolve() // <<<< this!
})
.catch(error => { reject() })
})
}
}
Now, in your component code, you can either do .then or async/await based on your preference. Here's how it would look with then:
const onSubmit = (data) => {
dispatch(Actions.updatedUser(data)).then(() => {
navigation.navigate('xxxx')
})
}
Here's a sandbox for an example