I have this reducer which shall return all comments on the page :
case actionTypes.GET_COMMENT:
return {
...state,
comments: action.comments
}
export const getComment = (comments : Object[]) => {
return {
type : actionTypes.GET_COMMENT,
comments
}
}
Here is how i call it in component
useEffect(() => {
const getAllCommentsOnCurrentPostFromBE = (id: Number) => {
axios.get(`http://localhost:4000/getComment/${id}`)
.then(res => {
console.log('--------res,get', res.data);
dispatch(actions.getComment(res.data))
console.log('--------posts', posts);
})
.catch(err => {
console.log('--------err', err);
})
}
getAllCommentsOnCurrentPostFromBE(grabIdFromLocation())
},[])
res.data is collection of key value pairs like this {"comment":"123"}
But it is not rendering anything,any suggestions please?
There is no dispatch() function. Downloaded data do not pass to the reducer. You have to use redux-thunk to use async functions with redux.
I recommend using actions in separate files:
export const fetchDataFromDatabase = () => async (
disapatch,
getState,
) => {
const response = await axios.get();
disapatch({
type: TYPE,
data: response.data,
});
};
Then export your component export default connect(yourProps,{fetchDataFromDatabase})(YourComponent)
In your component you can call props.fetchDataFromDatabase()
Related
Im trying to make my own Blog App but it doesn't seem to work as expected. Here are my codes for my "startSetMyBlogs":
export const startSetMyBlogs = () => {
return (dispatch, getState) => {
const myBlogs = [];
const blogRef = database.ref('blogs/')
const uid = getState().auth.uid;
//join table to get blogs match with logging in user
return database.ref(`author-blog`)
.once('value', snap => snap.val())
.then(childSnapshot => {
childSnapshot.forEach((blog) => {
if (blog.val() == uid) {
blogRef.child(blog.key).once('value').then(blogSnapshot => {
myBlogs.push({
id: blogSnapshot.key,
...blogSnapshot.val()
})
})
}
})
// Dispatch another action to set redux state.
dispatch(setUserBlogs(myBlogs));
})
}
}
my "setUserBlogs" action:
export const setUserBlogs = (myBlogs) => ({
type: 'SET_USER_BLOGS',
myBlogs
})
So how can I wait for "setUserBlogs" action to finish before passing props to my BlogList component ?
All you need to do is to store and pass on a loading state until your data is ready
export const startSetMyBlogs = () => {
return (dispatch, getState) => {
const myBlogs = [];
const blogRef = database.ref('blogs/')
const uid = getState().auth.uid;
//join table to get blogs match with logging in user
dispatch({type: 'SET_LOADING', payload: true});
return database.ref(`author-blog`)
.once('value', snap => snap.val())
.then(childSnapshot => {
childSnapshot.forEach((blog) => {
if (blog.val() == uid) {
blogRef.child(blog.key).once('value').then(blogSnapshot => {
myBlogs.push({
id: blogSnapshot.key,
...blogSnapshot.val()
})
})
}
})
// Dispatch another action to set redux state.
dispatch(setUserBlogs(myBlogs));
dispatch({type: 'SET_LOADING', payload: false});
})
}
}
Once you do that you can use this loading state in the component to render a loader
const mapStateToProps = state => {
blogs: state.blogs,
isLoading: state.isLoading
}
Use it as below: Dispatch an action once you get the as you want it from Firebase.
return database.ref(`author-blog`)
.once('value', snap => snap.val())
.then(childSnapshot => {
childSnapshot.forEach((blog) => {
if (blog.val() == uid) {
blogRef.child(blog.key).once('value').then(blogSnapshot => {
myBlogs.push({
id: blogSnapshot.key,
...blogSnapshot.val()
})
})
}
})
dispatch(setUserBlogs(myBlogs));
})
NOTE: API call is async so you have to wait for the data to assign it to state.
Hope this helps.
I am loading data from a public API after my component is mounted. When the data is loaded I am passing it to the reducer, but it always fires twice. This is what I have:
function MyComponent(props) {
function reducer(data, action) {
switch (action.type) {
case 'INITIALIZE':
return action.payload;
case 'ADD_NEW':
const newData = {...data};
newData.info.push({});
return newData;
}
}
const [data, dispatch] = React.useReducer(reducer, null);
useEffect(() => {
fetch(URL)
.then(response => {
dispatch({
type: 'INITIALIZE',
payload: response
});
})
.catch(error => {
console.log(error);
});
}, []);
const addNew = () => {
dispatch({ type: 'ADD_NEW' });
}
return(
<>data ? data.info.length : 'No Data Yet'</>
);
}
As you can see the component awaits for the data to populate the reducer, which, when INITIALIZE is also called twice, but I didn't care about it until I needed to call ADD_NEW, because in that case it adds two blank objects into the array instead of only one. I wen't into the documentation for side effects, but I was unable to solve it.
What is the best way to deal with this?
Here's how I would deal with the issue.
The main reason why it was re-running the action effect was because you had the reducer in the component's function. I also went ahead and fixed several other issues.
The fetch code was a little off due to how fetch works. You have to get the data type off of the response which gives another promise instead of the data directly.
You also needed to make the rendering use {} to indicate that you were using javascript rather than text.
import React, { useReducer, useState, useEffect } from "react";
import { render } from "react-dom";
import Hello from "./Hello";
import "./style.css";
const url = `https://picsum.photos/v2/list?page=3&limit=1`;
function App(props) {
const [data, dispatch] = React.useReducer(reducer, null);
useEffect(() => {
fetch(url)
.then(async response => {
dispatch({
type: "INITIALIZE",
payload: (await response.json())
});
})
.catch(error => {
console.log(error);
});
}, []);
const addNew = () => {
dispatch({ type: "ADD_NEW" });
};
console.log("here");
return (
<>
<div>{data ? JSON.stringify(data) : "No Data Yet"}</div>
<button onClick={addNew}>Test</button>
</>
);
}
render(<App />, document.getElementById("root"));
function reducer(data, action) {
switch (action.type) {
case "INITIALIZE":
console.log(action.payload, "Initialize");
return action.payload;
case "ADD_NEW":
const newData = { ...data };
newData.info = newData.info || [];
newData.info.push({});
console.log(newData);
return newData;
}
}
Guys i am having some trouble or quite doubtful.
am having one component and one reducer.
Reducer.js
import {
ASSET_POPUP_GET_ENDPOINT,
} from 'apiCollection';
import { performGet } from 'services/rest-service/rest-service';
export const GET_ASSETS_LIST = 'stories/GET_ASSETS_LIST';
const initialState = {
imgGroup: [],
isLoading: false,
};
const modalUploadReducer = (state = initialState, action) => {
switch (action.type) {
case GET_ASSETS_LIST: {
return {
...state,
ImageJson:action.payload.imageGroup,
};
}
case GET_ASSETS_LIST_ERROR: {
return {
...state,
isLoading:false,
};
}
default:
return state;
}
};
export const getModalClose = () => (dispatch) => {
dispatch({ type: CLOSE_MODAL });
}
export const getListActionDispactcher = () => (dispatch) => {
performGet(`${ASSET_POPUP_GET_ENDPOINT}`)
.then((response) => {
const payload = response.data;
dispatch({ type: GET_ASSETS_LIST,
payload: {
...payload,
data: payload.results,
} });
})
.catch((err) => {
dispatch({ type: GET_ASSETS_LIST_ERROR, payload: err });
throw err;
});
};
export default modalUploadReducer;
and my component look like
it do have mapStateToProps and mapDispatchToProps
and one of the function
const mapDispatchToProps = dispatch => ({
getCollection: () => dispatch(getListActionDispactcher()),
});
addDocumentClick = () =>{
this.props.getAssetsCollection();
}
and is it possible to have some setState/manipulation of response after api response got from reducer in the component
based on the response i need to do some changes in addDocumentClick.
Means something like this
addDocumentClick = () =>{
this.props.getAssetsCollection().then(...based on response;
}
The correct way for solving this is setting a global loading flag and in your componentDidUpdate() method, checking for the value to determine that the action has just succeeded. You already seem to have the isLoading flag. Just set it when the action's dispatched, and unset it after it succeeds/fails. And in componentDidUpdate():
function componentDidUpdate(prevProps) {
if (prevProps.isLoading && !this.props.isLoading) {
// do something
}
}
Of course, you need to connect() your loading flag to your component to achieve this.
If all you care about is whether the assets list has changed, you can simply check for the change of that prop in componentDidUpdate():
function componentDidUpdate(prevProps) {
if (prevProps.ImageJson !== this.props.ImageJson) {
// do something
}
}
Another solution is sending a callback to your action dispatcher, which makes your code more tightly coupled and I don't recommend, but it does work too. So, when you connect(), you can:
getCollection: (onSuccess) => dispatch(getListActionDispactcher(onSuccess)),
In your action dispatcher:
export const getListActionDispactcher = (onSuccess) => (dispatch) => {
// ...once API finished/failed
onSuccess(someData);
}
Finally, in your component:
this.props.getCollection((result) => {
console.log('succeeded!', result);
// hide modal, etc..
}
You are using redux-thunk, and calling thunk will return a promise which will resolve in whatever you return in your thunk. Therefore, all you need to do is to add return value to getListActionDispactcher
export const getListActionDispactcher = () => (dispatch) => {
// return this promise
return performGet(`${ASSET_POPUP_GET_ENDPOINT}`)
.then((response) => {
const payload = response.data;
dispatch({ type: GET_ASSETS_LIST,
payload: {
...payload,
data: payload.results,
} });
// return whatever you want from promise
return payload
})
.catch((err) => {
dispatch({ type: GET_ASSETS_LIST_ERROR, payload: err });
throw err;
});
};
.
addDocumentClick = () => {
this.props.getAssetsCollection().then(payload => console.log(payload))
}
You should, however, look for ways to avoid this pattern to have your components decoupled from actions as much as possible for the sake of modularity
Redux is harrrrd... At least to me, it is!!! Can someone please explain to me how can I pass this fetched json[0] through mapDispatchToProps to my action creator? And am I doing it right? I am using redux-thunk, is this the correct way of using it?
state = {
articles: {
article: []
}
};
qrCodeOnReadHandler = ({ data }) => {
this.props.onQRRead();
console.log(this.props.art);
fetch(data)
.then(response => response.json())
.then(json => {
console.log(json),
// () => this.props.onQRRead(json[0]),
this.setState({
...this.state,
articles: {
...this.state.articles,
article: json[0]
}
});
});
};
connecting redux
const mapStateToProps = state => {
return {
art: state.articles.article
};
};
const mapDispatchToProps = dispatch => {
return {
onQRRead: () => dispatch(article())
};
};
action creators
export const fetchedArticle = res => {
return {
type: ARTICLE,
res: res
};
};
export const article = res => {
return dispatch => {
dispatch(fetchedArticle(res));
};
};
how can I pass this fetched json[0] through mapDispatchToProps to my
action creator
You have to make your onQRRead receive an argument like this:
const mapDispatchToProps = dispatch => {
return {
onQRRead: payload => dispatch(article(payload))
};
};
The name of the function parameter is arbitrary.
For now, you can use it like the way you just did:
this.props.onQRRead(json[0])
My goal is dispatch one action after another. First the actionOne should be dispatched and next the actionTwo should be dispatched. I am very new to redux.
action.js
export const actionOne = (value) => ({
type: Explore.ACTION_ONE,
payload: { value },
});
export const actioneTwo = payload => ({
type: Explore.ACTION_TWO,
payload,
});
reducer.js
case Explore.ACTION_ONE: {
return {
...state,
tabs: somefunction(state),
checkFlag: true
};
}
case Explore.ACTION_TWO: {
return {
...state,
checkFlag: false
};
}
There is another container(in its epic.js) where I call the above action
export const getCountEpic = (action$, store) =>
action$.ofType(Constants.GET__COUNT).mergeMap(action => {
return getCount(action.payload) // This returns our Observable wrapping the Promise
.map(response => { //some code
return [actionOne(updatedPayload),actionTwo(updatedPayload)];
})
.catch(error => {
return [getCountRejected(error)];
})
.takeUntil(action$.ofType(AnnotationConstants.GET__COUNT_CANCELLED));
});
I am not able to dispatch actionwTwo and get error "Actions must be plain objects. Use custom middleware for async actions". what is correct way to dispatch after actionOne is finished?
It looks to me that you are returning an observable array, when the epic wants an observable object (or when two actions returned, a sequence of observable object).
This might be the pattern you require Process Manager dispatch multiple actions
export const getCountEpic = (action$, store) =>
action$.ofType(Constants.GET__COUNT)
.mergeMap(action => {
return getCount(action.payload)
.flatMap(response => {
//some code
return Observable.concat(
Observable.of(actionOne(updatedPayload)),
Observable.of(actionTwo(updatedPayload))
)
})
.catch(error => {
return [getCountRejected(error)];
})
.takeUntil(action$.ofType(AnnotationConstants.GET__COUNT_CANCELLED));
});
or you might get away with simpler
export const getCountEpic = (action$, store) =>
action$.ofType(Constants.GET__COUNT)
.mergeMap(action => {
return getCount(action.payload)
.map(response => {
//some code
return Observable.of(
actionOne(updatedPayload),
actionTwo(updatedPayload)
)
})
...