MY action
const fetchDataApi = (getState) => {
let { data } = getState()
return axios.get('http://api.openweathermap.org/data/2.5/weather?q=london,uk&appid=26aacf43db7ecfa2ecd85500eaee9920').then(thunkdata => {
console.log(thunkdata)
return {
[data]: thunkdata
}
})
}
const fetchgetDataCall = () => {
return (dispatch, getState) => {
return dispatch(fetchDataApi(getState))
}
}
export const getData = (dispatch) => {
dispatch(fetchgetDataCall())
return {
type: actionTypes.GETDATA,
}
}
In action.js i want to get data from my whether api and store in data, so i am using getstate to get data variable and assign data to it
My calender Component where i am connecting my callender to actionType
import React, { Component } from 'react';
// import 'moment/locale/it.js';
import { DatePicker, DatePickerInput } from 'rc-datepicker';
// import { ca } from 'date-fns/esm/locale';
import 'rc-datepicker/lib/style.css';
import { connect } from 'react-redux';
import { getData } from '../store/actions/actions'
const date = '2015-06-26' // or Date or Moment.js
class Callender extends Component {
//These is a method es7
onChangeandler = (jsDate, dateString, event) => {
// event.preventDefault()
console.log("[we are lokking at js date]",jsDate);
this.props.getWether();
console.log("[we are seeing the props storeDta]",this.props.storeData);
}
//Next method
render() {
return (
<div>
<DatePicker onChange={this.onChangeandler} value={date} />
</div>
)
}
}
const mapStateToProps = state =>({
storeData: state.data
})
const mapDispatchToProps = (dispatch) =>({
getWether: () => dispatch(getData())
})
export default connect(mapStateToProps,mapDispatchToProps)(Callender)
My reducer
import * as actionType from '../actions/actionTypes';
const intialState ={
time:null,
day:null,
data:null
}
// reducer
const reducer = (state=intialState, action) =>{
switch(action.type){
case actionType.GETDATA:
return {
...state,
data:action.data
}
case actionType.POSTDATA:
return {
...state
}
default :
return {
...state
}
}
}
export default reducer;
actionTypes.js
export const POSTDATA="POSTDATA";
export const GETDATA = "GETDATA";
1)I am calling my action creator in callender.js file
2) Where i am using thunk middleware to get data ,and store in data variable from redux store
3)I can't find the issue please help me
Your actions looks quite weird. The getData action creator disptaches fetchgetDataCall which dispatches fetchDataApi and that returns just some object { [data]: thunkdata} where property data are probably null in that moment. Hence there are not any properties type or data in your action object.
The second thing what your getData do is returning the object {type: actionTypes.GETDATA}, hence there is not any property data in your action object.
Try to do it something like this (updated according to #mbojko answer):
const getData = () => {
return (dispatch) => {
return axios.get('http://api.openweathermap.org/data/2.5/weather?q=london,uk&appid=26aacf43db7ecfa2ecd85500eaee9920').then(thunkdata => {
return dispatch({
type: actionTypes.GETDATA,
data: thunkdata
})
})
}
}
Compare your function signature
export const getData = (dispatch) => {
With how you call it:
const mapDispatchToProps = (dispatch) =>({
getWether: () => dispatch(getData())
})
The argument is missing (therefore dispatch is not defined and obviously not a function).
Should be dispatch(getData(dispatch)), probably.
Related
I'm a newbie in redux and react.js,
I am trying to make a button disappear on a component in react.js by putting an if condition on the state variable (articlesTable/index.js), which is connected to the redux library function on another file (actions/actionArticles.js), when a button on articlesTable/index.js is clicked, the component is connected with actions/actionArticles.js and dispatch a function in actions/actionArticles.js, which is called loadMoreData().
The function I am trying to configure the state in redux is,
in articlesActions.js
export const loadMoreArticles = () => async (dispatch, getState) => {
const lastArticleKey = Object.keys(getState().articlesMap).pop();
const lastArticle = getState().articlesMap[lastArticleKey];
console.log("articleMap", getState().articlesMap);
console.log("Last article", lastArticleKey, lastArticle);
let filteredArticles = {};
const uid = getState().auth.uid;
const userLevel = getState().profile.userLevel;
} else {
const filteredArticlesArray = [];
var lastArticleReached = false;
...
var lastArticleInArray = filteredArticlesArray[filteredArticlesArray.length-1];
if (lastArticleInArray[0]===lastArticleKey) {
console.log("Bingo, last article reached!");
lastArticleReached = true;
}
else if (lastArticleInArray[0]!== lastArticleKey)
{
console.log("Not last article");
lastArticleReached = false;
}
filteredArticles = Object.fromEntries(filteredArticlesArray.reverse());
}
dispatch({type: LAST_ARTICLE_REACHED, payload: lastArticleReached})
...
};
I dispatch this function with
dispatch({ type: LOAD_MORE_ARTICLES, payload: filteredArticles });
in the code snippet above
The root reducer looks like this,
reducers/index.js
import { combineReducers } from 'redux';
import { reducer as formReducer } from 'redux-form';
import articlesStatusReducer from './articlesStatusReducer';
const rootReducer = combineReducers({
...
articlesStatus: articlesStatusReducer,
form: formReducer,
...
});
export default rootReducer;
In articleStatusReducer,
import {LAST_ARTICLE_REACHED} from "../actions/types";
export default function(state = {}, action) {
switch(action.type) {
case(LAST_ARTICLE_REACHED):
console.log(action.payload);
return action.payload;
default:
return state;
}
}
In the articlesTable/index.js, I connect like this
const mapStateToProps = (state) => {
return {
articlesMap: state.articlesMap,
appStatus: state.appStatus,
profile: state.profile,
lastArticleReached: state.articlesStatus,
}
};
const mapDispatchToProps = (dispatch) => {
return {
getArticlesWithData: () => dispatch(getArticlesWithData()),
loadMore: () => dispatch(loadMoreArticles())
}
};
export default compose(
withRouter,
connect(mapStateToProps, mapDispatchToProps)
)(ArticlesTable)
For some reason, articleStatus isn't recognised and when I do
console.log(this.props.articleStatus)
state.articleStatus is undefined
How can I reference state.articleStatus which should be boolean ?
Edit:
For some reason when I put it in a conditional JSX brackets in the render method, it prints out undefined
render () => {
{
console.log(this.props.lastArticleReached),
!this.props.lastArticleReached
: <Button> </Button>
?
<div><div>
}
}``
In function mapStateToProps, you should map state.articleStatus to a props.
somethings like this:
const mapStateToProps = (state) => {
return {
articlesMap: state.articlesMap,
appStatus: state.appStatus,
profile: state.profile,
lastArticleReached: state.articlesStatus,
articleStatus: state.articleStatus
}
};
So this.props.articleStatus will works . :)
The problem is in your reducer. Each case of your reducer must return the state but in your case, your return action.payload.
try something like this.
case(LAST_ARTICLE_REACHED):
console.log(action.payload);
return {...state, articleStatus: action.payload};
like this, articlesStatus became an object with one props, articleStatus, your boolean.
I tried another name for the props but with similar method as Thomas Caillard,
Reducer.js
case(REACH_LAST_ARTICLE):
return {...state, lastArticleReached: action.payload}
in component index.js
const mapStateToProps = (state) => {
return {
...
lastArticleReached: state.articlesMap.lastArticleReached
...
}
};
Thanks for all the helps so far
I'v tried so many way to fetch data only once before rendering but have some issue:
1) I Can't call dispatch in componentDidMount because there is the rule that I can do it in Functional component only
2) If I try to call fetch function in the beginning of a Functional component it starts to rerender infinitely because fetch function calls every time and change a state in a redux store
3) I found a solution with useEffect but it generate exception "Invalid hook call" like in first point
How can I call fetch function only once in this component?
here is my component:
import React, { useEffect } from "react";
import { useParams as params} from "react-router-dom";
import { VolunteerCardList } from "./VolunteerCardList";
import { AnimalNeeds } from "./AnimalNeeds";
import { AppState } from "../reducers/rootReducer";
import { connect } from "react-redux";
import { Page404 } from "./404";
import { fetchAnimal } from "../actions/animalAction";
import { Dispatch } from "redux";
import { IAnimalCard } from "../interfaces/Interfaces";
const AnimalCard: React.FC<Props> = ({animal, loading, fetch}) => {
useEffect(() => {
fetch(); //invalid hook call????
}, [])
return (
<div className="container">
some html
</div>
)
}
interface RouteParams {
shelterid: string,
animalid: string,
}
interface mapStateToPropsType {
animal: IAnimalCard,
loading : boolean
}
const mapStateToProps = (state: AppState) : mapStateToPropsType=> {
return{
animal: state.animals.animal,
loading: state.app.loading
}
}
interface mapDispatchToPropsType {
fetch: () => void;
}
const mapDispatchToProps = (dispatch: Dispatch<any>) : mapDispatchToPropsType => ({
fetch : () => {
const route = params<RouteParams>();
dispatch(fetchAnimal(route.shelterid, route.animalid));
}
})
type Props = ReturnType<typeof mapStateToProps> & ReturnType<typeof mapDispatchToProps>;
export default connect(mapStateToProps, mapDispatchToProps as any)(AnimalCard);
this is my reducer:
export const animalReducer = (state: AnimalReducerType = initState, action: IAction) => {
switch (action.type) {
case AnimalTypes.FETCH_ANIMAL:
return {...state, animal: action.payload};
break;
default:
return state;
break;
}
this is action:
export interface IFetchAnimalAction {
type: AnimalTypes.FETCH_ANIMAL,
payload: IAnimalCard
}
export type IAction = IFetchAnimalAction;
export const fetchAnimal = (shelterId : string, animalId: string) => {
return async (dispatch: Dispatch) => {
const response = await fetch(`https://localhost:44300/api/animals/${animalId}`);
const json = await response.json();
dispatch<IFetchAnimalAction>({type: AnimalTypes.FETCH_ANIMAL, payload: json})
}
}
This runs as old lifecycle method componentDidMount:
useEffect(() => {
fetch(); //invalid hook call????
}, [])
I guess the behaviour you want to replicate is the one iterated by componentWillMount, which you cannot do by any of the standard hooks. My go-to solution for this is to let the acquire some loadingState, most explicitly as:
const AnimalCard: React.FC<Props> = ({animal, loading, fetch}) => {
const [isLoading, setIsLoading] = useState<boolean>(false);
useEffect(() => {
fetch().then(res => {
// Do whatever with res
setIsLoading(true);
}
}, [])
if(!isLoading){
return null
}
return (
<div className="container">
some html
</div>
)
}
Need help in getting response from a function written inside reducer function
functional component
import {
getAssets,
} from '../../reducers';
const myFunctionalComponent = (props) => {
const dispatch = useDispatch();
const onLinkClick = () => {
dispatch(getAssets());
}
}
return (
<div>
<mainContent />
</div>
)
}
In my reducer
const reducer = (state = initialState, action) => {
switch (action.type) {
case ASSETS_LIST: {
return {
...state,
successToast: true,
isLoading: false,
data: action.payload,
};
}
}
export const listsDispactcher = () => dispatch => {
dispatch({ type: SHOW_LOADER });
performGet(ENDPOINT URL)
.then(response => {
debugger;
const payload = response.data;
dispatch({
type: ASSETS_LIST,
payload: {
...payload,
data: payload.results,
},
});
dispatch({ type: HIDE_LOADER });
})
.catch(err => {
dispatch({ type: GET_ASSETS_ERROR, payload: err });
);
});
};
when i click the link ,am getting my api called in function in reducer and its getting response in newtwork tab in developer console , but how to get the response (that is successToast,data,isLoading )in my functional component and to pass the same to child components ?
I advice you to change the structure of your project. Place all your network calls in a file and call them from your component. It is better for readability and understandability
import {
getAssets,
} from './actions';
const myFunctionalComponent = (props) => {
const dispatch = useDispatch();
const onLinkClick = async () => {
const data = await dispatch(getAssets());
}
}
return (
<div>
<mainContent />
</div>
)
}
In ./actions.js
const getAssets =()=>async dispatch =>{
const res = await axios.get();
dispatch(setYourReduxState(res.data));
return res.data;
}
Now your component will get the data of network call. and Your redux state also will get update
For functional components, to access state stored centrally in redux you need to use useSelector hook from react-redux
import React from 'react'
import { useSelector } from 'react-redux'
export const CounterComponent = () => {
const counter = useSelector(state => state.counter)
return <div>{counter}</div>
}
Official doc:
https://react-redux.js.org/api/hooks#useselector-examples
Also found this working example for you to refer.
https://codesandbox.io/s/8l0sv
I want my component to fetch an array of objects from the server. Each object is a message with author, body and date. I then want to render these messages in my react component.
My react component currently fetches data from the server before mounting. It will then store this message list in the redux state.|
I'm sure there's a better way of writing this code.
1. Can I place the fetch request in either the Action or Reducer file?
2. Can I write a function in the component to make the async call?
import React, { Component } from 'react';
import Message from '../components/message.jsx';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
// Actions
import { fetchMessages } from '../actions/actions_index.js';
class MessageList extends Component {
constructor(props) {
super(props)
}
componentWillMount() {
fetch('https://wagon-chat.herokuapp.com/general/messages')
.then(response => response.json(),
error => console.log('An error occured receiving messages', error))
.then((data) => {
this.props.fetchMessages(data.messages);
});
}
render() {
return (
<div className="message-list">
{this.props.messageList.map( (message, index) => { return <Message key={index} message={message}/> })}
</div>
)
}
}
function mapStateToProps(state) {
return {
messageList: state.messageList
}
}
function mapDispatchToProps(dispatch) {
return bindActionCreators(
{ fetchMessages: fetchMessages },
dispatch
)
}
export default connect(mapStateToProps, mapDispatchToProps)(MessageList);
Can I place the fetch request in either the Action or Reducer file?
The fetch request should be placed in action creator. Where the retrieved data will be dispatched to reducer later to manipulate the data, and lastly update the store to show on UI. Here's simple flow for most of react-redux app.
UI -> Action creator (calling request, saga etc..) -> reducer -> store -> UI
Can I write a function in the component to make the async call?
Yes, this should be called action creator, and you can see actions.js below for more reference.
I think you can safely follow this sample pattern where most tutorials out there apply. I'm assuming all files listed here are in the same directory.
constant.js
const MESSAGE_FETCH__SUCCESS = 'MESSAGE/FETCH__SUCCESS'
const MESSAGE_FETCH__ERROR = 'MESSAGE/FETCH__ERROR'
export {
MESSAGE_FETCH__SUCCESS,
MESSAGE_FETCH__ERROR
}
actions.js
import {
MESSAGE_FETCH__SUCCESS,
MESSAGE_FETCH__ERROR
} from './constant';
const fetchMessageError = () => ({
type: MESSAGE_FETCH__ERROR
})
const fetchMessageSuccess = data => ({
type: MESSAGE_FETCH__SUCCESS,
payload: data
})
const fetchMessages = () => {
const data = fetch(...);
// if error
if (data.error)
fetchMessageError();
else fetchMessageSuccess(data.data);
}
export {
fetchMessages
}
reducers.js
import {
MESSAGE_FETCH__SUCCESS,
MESSAGE_FETCH__ERROR
} from './constant';
const INIT_STATE = {
messageList: []
}
export default function( state = INIT_STATE, action ) {
switch(action.type) {
case MESSAGE_FETCH__SUCCESS:
return {
...state,
messageList: action.payload
}
case MESSAGE_FETCH__ERROR:
// Do whatever you want here for an error case
return {
...state
}
default:
return state;
}
}
index.js
Please read the comment I noted
import React, { Component } from 'react';
import Message from '../components/message.jsx';
import { connect } from 'react-redux';
// Actions
import { fetchMessages } from './actions';
class MessageList extends Component {
/* If you don't do anything in the constructor, it's okay to remove calling `constructor(props)`
*/
//constructor(props) {
// super(props)
//}
// I usually put this async call in `componentDidMount` method
componentWillMount() {
this.props.fetchMessage();
}
render() {
return (
<div className="message-list">
{
/* Each message should have an unique id so they can be used
for `key` index. Do not use `index` as an value to `key`.
See this useful link for more reference: https://stackoverflow.com/questions/28329382/understanding-unique-keys-for-array-children-in-react-js
*/
this.props.messageList.map( message => <Message key={message.id} message={message}/> )
}
</div>
)
}
}
function mapStateToProps(state) {
return {
messageList: state.messageList
}
}
export default connect(mapStateToProps, {
fetchMessages
})(MessageList);
You could use redux-thunk in an action called getMessages.
So:
(The double arrow func, is to return an action, see redux-thunk)
const getMessages = ()=>(dispatch, getState)=>{
fetch('https://wagon-chat.herokuapp.com/general/messages')
.then(response => response.json(),
error => dispatch(['error', error]))
.then((data) => {
dispatch(data);
})
}
Then you've successfully reduced your component to:
componentWillMount(){
this.props.getMessages()
}
I think #Duc_Hong answered the question.
And in my opinion, I suggest using the side-effect middle-ware to make AJAX call more structured, so that we could handle more complicated scenarios (e.g. cancel the ajax request, multiple request in the same time) and make it more testable.
Here's the code snippet using Redux Saga
// Actions.js
const FOO_FETCH_START = 'FOO\FETCH_START'
function action(type, payload={}) {
return {type, payload};
}
export const startFetch = () => action{FOO_FETCH_START, payload);
// reducer.js
export const foo = (state = {status: 'loading'}, action) => {
switch (action.type) {
case FOO_FETCH_STARTED: {
return _.assign({}, state, {status: 'start fetching', foo: null});
}
case FOO_FETCH_SUCCESS: {
return _.assign({}, state, {status: 'success', foo: action.data});
}
......
}
};
Can I place the fetch request in either the Action or Reducer file?
// Saga.js, I put the ajax call (fetch, axios whatever you want) here.
export function* fetchFoo() {
const response = yield call(fetch, url);
yield put({type: FOO_FETCH_SUCCESS, reponse.data});
}
// This function will be used in `rootSaga()`, it's a listener for the action FOO_FETCH_START
export function* fooSagas() {
yield takeEvery(FOO_FETCH_START, fetchFoo);
}
Can I write a function in the component to make the async call?
// React component, I trigger the fetch by an action creation in componentDidMount
class Foo extends React.Component {
componentDidMount() {
this.props.startFetch();
}
render() {
<div>
{this.props.foo.data ? this.props.foo.data : 'Loading....'}
<div>
}
}
const mapStateToProps = (state) => ({foo: state.foo});
const mapDispatchToProps = { startFetch }
export default connect(mapStateToProps, mapDispatchToProps) (Foo);
//client.js, link up saga, redux, and React Component
const render = App => {
const sagaMiddleware = createSagaMiddleware();
const store = createStore(
combinedReducers,
initialState,
composeEnhancers(applyMiddleware(sagaMiddleware))
);
store.runSaga(rootSaga);
return ReactDOM.hydrate(
<ReduxProvider store={store}>
<BrowserRouter><AppContainer><App/></AppContainer></BrowserRouter>
</ReduxProvider>,
document.getElementById('root')
);
}
So i'm doing a API GET request and set the data on reducer, but the component render twice, first before dispatch and another after, the first one is causing map function problem
what can i do to avoid render twice and solve map function problem?
App.js
componentDidMount(){
this.props.carregarLojas();
}
render(){
const { lojasTeste } = this.props;
//rendering 2 times
console.log(lojasTeste);
return(
<div>
lojasTeste.map((i, index) => (
<h1>{i.name}</h1>
))
</div>
)
}
const mapStateToProps = store => ({
lojasTeste: store.lojaState.lojasTeste
});
const mapDispatchToProps = dispatch => {
return {
carregarLojas: () => {
dispatch(carregarLojas());
}
};
};
export default connect(mapStateToProps, mapDispatchToProps)(App);
Action.js
export const setarLojas = (lojas) =>{
return {
type: SETAR_LOJAS,
data: lojas
}
}
export const carregarLojas = () => {
return (dispatch) => {
return API.get('loja')
.then(response => {
dispatch(setarLojas(response.data))
})
.catch(error => {
throw(error);
})
}
Reducer.js
const initialState ={
lojasTeste: {}
}
export const lojaReducer = (state = initialState, action) => {
switch (action.type){
case SETAR_LOJAS:
return {
...state,
lojasTeste: action.data
}
default:
return state;
}
}
The double render is totally normal:
Your component render once, then call the carregarLojas method which is async. When resolved, the method will update your redux store, which is connected with the props of your component (mapStateToProps). When a prop is updated, it cause automatically a rerender.
Also, for your map problem, you didn't initialized lojasTeste as an array, but as an object. You can't use map on an object (cf https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Array/map)