Can anyone tell me what I am doing wrong here. I am trying to fetch cities using API but componentDidMount nor componentWillMount is working.
I have tested my getWeather function using button, the function is working but when I try to call the it using componentDidMount or componentWillMount it is not working...
My code below:
import React, { Component } from 'react';
import { StyleSheet, Text, View } from 'react-native';
import Header from './Header';
import { TextInput, Card, Button, Title } from 'react-native-paper';
import { useState } from 'react';
import { FlatList } from 'react-native-gesture-handler';
export default function Home() {
const [info, setInfo] = useState([{
city_name: "loading !!",
temp: "loading",
humidity: "loading",
desc: "loading",
icon: "loading"
}]);
getWeather = () => {
console.log("Hello Weather");
fetch("https://api.openweathermap.org/data/2.5/find?q=London&units=metric&appid={MY_API_KEY}")
.then(res => res.json())
.then(data => {
console.log(data);
/*setInfo([{
city_name: data.name,
temp: data.main.temp,
humidity: data.main.humidity,
desc: data.weather[0].description,
icon: data.weather[0].icon}]);
*/
})
}
componentWillMount = () => {
console.log("Hello Will");
this.getWeather();
}
componentDidMount = () => {
console.log("Hello Did");
this.getWeather();
}
return (
<View style={styles.container}>
<Header title="Current Weather"/>
<Card style = {{margin: 20}}>
<View>
<Title>{info.city_name}</Title>
<Title>{info.desc}</Title>
</View>
</Card>
<Button onPress={() => this.getWeather()} style={{margin: 40, padding: 20}}>Testing</Button>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
});
componentDidMount and componentWillMount only work in class based React components; what you have here is a functional component. You can use the useEffect hook to accomplish the same.
useEffect(() => {
getWeather();
}, []);
Note that this does not exist in functional components; you can just call the function directly after you have declared it.
If you haven't used useEffect before, you may have questions about the array as the second argument. If it is empty, it will run on mount and will run what you return from the first argument on unmount. If you want to run your effect again, add an dependencies into the array.
ComponentDidMount would only work in class components. Using the useEffect React hook would achieve the same effect without issues
Related
I am making a todo/shopping list in ReactJS. Besides being able to add items manually to the list by input, the user should also be able to add items programmatically.
I am using createContext() and useReducer for managing the state().
When I add items programmatically by providing an array through the props and listen for changes in useEffect, the useEffect and dispatch fires twice despite that I only changed the props once.
However this is NOT happening when I provide the array of items through props the first time.
Consequently, after the first time, when dispatch fires twice the list get duplicates (also duplicate keys).
Is it happening due to some re-rendering process that I am not aware of? Any help is much appreciated as I am really stuck on this one.
Here is the code:
Context provider component containing the useEffect that triggers the dispatch method from useReducer when the props change:
import React, { createContext, useEffect, useReducer } from 'react';
import todosReducer from '../reducers/todos.reducer';
import { ADD_INGREDIENT_ARRAY } from '../constants/actions';
const defaultItems = [
{ id: '0', task: 'Item1', completed: false },
{ id: '1', task: 'Item2', completed: false },
{ id: '2', task: 'Item3', completed: false }
];
export const TodosContext = createContext();
export const DispatchContext = createContext();
export function TodosProvider(props) {
const [todos, dispatch] = useReducer(todosReducer, defaultItems)
useEffect(() => {
if (props.ingredientArray.length) {
dispatch({ type: ADD_INGREDIENT_ARRAY, task: props.ingredientArray });
}
}, [props.ingredientArray])
return (
<TodosContext.Provider value={todos}>
<DispatchContext.Provider value={dispatch}>
{props.children}
</DispatchContext.Provider>
</TodosContext.Provider>
);
}
My reducer function (ADD_INGREDIENT_ARRAY is the one that gets called from above code snippet) :
import uuidv4 from "uuid/dist/v4";
import { useReducer } from "react";
import {
ADD_TODO,
REMOVE_TODO,
TOGGLE_TODO,
EDIT_TODO,
ADD_INGREDIENT_ARRAY
} from '../constants/actions';
const reducer = (state, action) => {
switch (action.type) {
case ADD_TODO:
return [{ id: uuidv4(), task: action.task, completed: false }, ...state];
case REMOVE_TODO:
return state.filter(todo => todo.id !== action.id);
case TOGGLE_TODO:
return state.map(todo =>
todo.id === action.id ? { ...todo, completed: !todo.completed } : todo
);
case EDIT_TODO:
return state.map(todo =>
todo.id === action.id ? { ...todo, task: action.task } : todo
);
case ADD_INGREDIENT_ARRAY:
console.log('THE REDUCER WAS CALLED')
return [...action.task.map(ingr => ({ id: uuidv4(), task: ingr.name, completed: false }) ), ...state]
default:
return state;
}
};
export default reducer;
The list component that renders each item and uses the context from above code snippet:
import React, { useContext, useEffect, useState } from 'react';
import { TodosContext, DispatchContext } from '../contexts/todos.context';
import Todo from './Todo';
function TodoList() {
const todos = useContext(TodosContext);
return (
<ul style={{ paddingLeft: 10, width: "95%" }}>
{todos.map(todo => (
<Todo key={Math.random()} {...todo} />
))}
</ul>
);
}
export default TodoList;
And the app component containing the list which is wrapped in the context provider that passes the props:
import React, { useEffect, useReducer } from 'react';
import { TodosProvider } from '../contexts/todos.context';
import TodoForm from './TodoForm';
import TodoList from './TodoList';
function TodoApp({ ingredientArray }) {
return (
<TodosProvider ingredientArray={ingredientArray}>
<TodoForm/>
<TodoList/>
</TodosProvider>
);
}
export default TodoApp;
And the top level component that passes the props as well:
import React, { useEffect, useContext } from 'react';
import TodoApp from './TodoApp';
import useStyles from '../styles/AppStyles';
import Paper from '#material-ui/core/Paper';
function App({ ingredientArray }) {
const classes = useStyles();
return (
<Paper className={classes.paper} elevation={3}>
<div className={classes.App}>
<header className={classes.header}>
<h1>
Shoppinglist
</h1>
</header>
<TodoApp ingredientArray={ingredientArray} />
</div>
</Paper>
);
}
export default App;
The parent component where ingredientArray is made. It takes the last recipe in the state.recipes array and passes it as props to the shoppingList:
...
const handleSetNewRecipe = (recipe) => {
recipe.date = state.date;
setState({ ...state, recipes: [...state.recipes, recipe] })
}
...
{recipesOpen ? <RecipeDialog
visible={recipesOpen}
setVisible={setRecipesOpen}
chosenRecipe={handleSetNewRecipe}
/> : null}
...
<Grid item className={classes.textAreaGrid}>
<ShoppingList ingredientArray={state.recipes.length ? state.recipes.reverse()[0].ingredients : []}/>
</Grid>
....
What am I doing wrong?
Glad we got this sorted. As per the comments on the main post, mutating React state directly instead of updating it via a setter function can cause the actual value of the state to become out of sync with dependent components and effects further down the tree.
I still can't completely reason why it would be causing your specific issue in this case, but regardless, removing the mutative call to reverse and replacing it with this simple index calculation appears to have solved the issue:
state.recipies[state.recipies.length-1].ingredients
I am trying to link a React Native <RefreshControl> component to my app in such a way that it triggers a new dispatch to my redux store. However upon scrolling down to trigger the refresh the loading wheel briefly spins before returning the following error: TypeError: undefined is not a function (near '...cards.map...')
My component code and redux functions are below:
Component Code
import React, {Component} from 'react';
import {Text, View, ScrollView, RefreshControl} from 'react-native';
import {connect} from 'react-redux';
import {fetchBalanceActionCreator, fetchPriceActionCreator} from './actions';
import ItemCard from './ItemCard';
class StoreItems extends Component {
render() {
return (
<View style={{flex: 1}}>
<ScrollView
scrollEventThrottle={16}
directionalLockEnabled={true}
refreshControl={
<RefreshControl
refreshing={this.props.refreshing}
onRefresh={() => {
console.log('+++ onRefresh');
this.props.fetchData(this.props.cards);
}}
/>
}
horizontal={false}>
{ITEMS.map(item => {
return (
<ItemCard name={item.name} price={item.price} qty={item.qty} />
);
})}
</ScrollView>
</View>
);
}
}
const mapStateToProps = state => {
console.log(
'Data +++ refreshing',
state.itemPrice.isFetching || state.cardBalance.isFetching,
);
return {
refreshing: state.itemPrice.isFetching || state.cardBalance.isFetching,
cards: state.cards,
};
};
const mapDispatchToProps = dispatch => {
return {
fetchData: cards => {
console.log('fetchData1');
dispatch(fetchBalanceActionCreator(cards));
dispatch(fetchPriceActionCreator(cards));
console.log('fetchData2');
},
};
};
export default connect(
mapStateToProps,
mapDispatchToProps,
)(StoreItems);
Redux Functions
function fetchPriceActionCreator(items) {
return function(dispatch) {
dispatch(requestItemPriceActionCreator());
const reqs = items.map(item => {
return getItemPrice(item.itemNumber);
});
return Promise.all(reqs).then(res => {
dispatch(receiveItemPriceActionCreator(res));
});
};
}
function fetchBalanceActionCreator(cards) {
return function(dispatch) {
dispatch(requestBalanceActionCreator());
const reqs = cards.map(card => {
const cardType = card.cardType.toLowerCase();
const cardNumber = card.cardNumber;
return getBalances(cardType, cardNumber);
});
return Promise.all(reqs)
.then(balances => {
dispatch(receiveBalanceActionCreator(balances));
})
.catch(err => {
console.error(err);
});
};
}
EDIT:
The getBalances and getPrices functions are both making API calls.
Most of the data fetched from these calls is displayed in child components/elsewhere in the app, but I want to be able to refresh on this particular screen.
I have added a number of console.logs throughout the code, the results are as follows:
+++ onRefresh displays in the console
fetchData1 follows immediately after
data +++ refreshing is then displayed, I have noticed occasionally
it shows a value of true, and occasionally a value of false
fetchData2 is never logged to the console.
The error screen in the app simulator references the line with dispatch(fetchBalanceActionCreator(cards)); from the StoreItems component, in addition to the return function(dispatch) {line from the Redux action creator file.
EDIT 2
Here is the getBalance function:
async function getBalances(cardType, card) {
cardType = cardType.toLowerCase();
const balance = await balanceRequestor[cardType](card);
return {
cardId: cardType,
card: card,
balance: balance,
};
}
Depending on the card type, getBalance will then trigger a function fetches data from an API and looks like so:
export async function getCard1Balance(card) {
const response = await fetch(`https://api.exampleapi.com/${card}`);
const data = await response.json();
return data;
}
All of these functions work fine upon loading and navigating the app, it only seems to be when using the RefreshControl that the undefined is not a function is shown.
First, I am really new to react; so, apologies, for beginner questions.
I have a React app with Redux and Redux Saga.
One of the components looks like this:
import { TableContainer, TableHead, TableRow } from '#material-ui/core';
import Paper from '#material-ui/core/Paper';
import makeStyles from '#material-ui/core/styles/makeStyles';
import Table from '#material-ui/core/Table';
import TableBody from '#material-ui/core/TableBody';
import TableCell from '#material-ui/core/TableCell';
import React, { useEffect } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { ProgramCategory } from '../model/program-category';
import { ProgramCategoryItemRow } from '../ProgramGategoryItemRow/ProgramCategoryItemRow';
import { ProgramCategoryActions } from '../store/program-category.actions';
import { ProgramCategorySelectors } from '../store/program-category.selectors';
const useStyles = makeStyles({
table: {
width: '100%',
},
tableHeadCell: {
fontWeight: 'bold',
},
});
export interface ProgramCategoriesTableProps {
isLoaded: boolean;
categories: ProgramCategory[];
fetchAllCategories: () => void;
}
export const PureProgramCategoriesTable: React.FC<ProgramCategoriesTableProps> = ({
isLoaded,
categories,
fetchAllCategories,
}) => {
useEffect(() => {
console.error('in useEffect');
fetchAllCategories();
});
const styles = useStyles();
return (
<TableContainer component={Paper}>
// the rest
<TableBody>
{categories.map(c => (
<ProgramCategoryItemRow category={c} />
))}
</TableBody>
</TableContainer>
);
};
const mapStateToProps = createSelector(
[ProgramCategorySelectors.isLoaded, ProgramCategorySelectors.getAll],
(isLoaded, categories) => ({ isLoaded, categories }),
);
const mapDispatchToProps = {
fetchAllCategories: ProgramCategoryActions.fetchAll.start,
};
export const ProgramCategoriesTable = connect(mapStateToProps, mapDispatchToProps)(PureProgramCategoriesTable);
The sagas that process ProgramCategoryActions.fetchAll.start is as follows:
import { call, put, takeLatest } from 'redux-saga/effects';
import { ProgramCategoryApi } from '../services/program-category.api';
import { ProgramCategoryActions } from './program-category.actions';
function* handleFetchAll() {
try {
const categories = yield call(ProgramCategoryApi.fetchAll);
yield put(ProgramCategoryActions.fetchAll.success(categories));
} catch (e) {
yield put(ProgramCategoryActions.fetchAll.failure(e));
}
}
export function* programCategorySagas() {
yield takeLatest(ProgramCategoryActions.fetchAll.start.type, handleFetchAll);
}
Everything make sense, but what happens my action code is executed over and over again. Digging into it a bit more, it appears that the effect hook is also executed over and over again.
If I understand it correctly, it happens because the data in state is changing, the component is getting re-rendered again. But, it leads to infinite loop.
What am I doing wrong? What is the correct way to setup this kind of component?
One of the options that I found is to change the saga to:
function* handleFetchAll() {
try {
const alreadyLoaded = select(ProgramCategorySelectors.isLoaded);
if (!alreadyLoaded) {
const categories = yield call(ProgramCategoryApi.fetchAll);
yield put(ProgramCategoryActions.fetchAll.success(categories));
}
} catch (e) {
yield put(ProgramCategoryActions.fetchAll.failure(e));
}
}
So, it only calls the api once; and it seem to work fine this way. But, is it the correct solution?
As suggested in the comments, I tried adding dependency to the effect:
useEffect(() => {
fetchAllCategories();
}, []);
Now, I am getting an error:
./src/program/ProgramCategoriesTable/ProgramCategoriesTable.tsx Line
37:6: React Hook useEffect has a missing dependency:
'fetchAllCategories'. Either include it or remove the dependency
array. If 'fetchAllCategories' changes too often, find the parent
component that defines it and wrap that definition in useCallback
react-hooks/exhaustive-deps
The issue is here,
useEffect(() => {
console.error('in useEffect');
fetchAllCategories();
});
From react docs: Does useEffect run after every render? Yes! By default, it runs both after the first render and after every update. (We will later talk about how to customize this.) Instead of thinking in terms of “mounting” and “updating”, you might find it easier to think that effects happen “after render”. React guarantees the DOM has been updated by the time it runs the effects.
https://reactjs.org/docs/hooks-effect.html
You have to pass and array of dependecies at the end.
useEffect(() => {
console.error('in useEffect');
fetchAllCategories();
}, []);
Hope this helps!
in react native state is commonly used and really important if we have using data to manipulate or displaying in our phone, react native have a problem with state of course, but redux came to solve that struggle stuff, I'm still newbie guy in react native, and just understand how to fetch API in 'old' way not using redux, but how ? in reducer I'm trying to call my function and callback in my component, but didn't work, here is the code :
peopleReducer.js :
import { ADD_FRIENDS } from "../types";
import { INIT_PEOPLE } from "../states";
const peopleReducers = (state = INIT_PEOPLE, action) => {
switch (action.type) {
// // save image
case ADD_FRIENDS:
// find how to fetch API in react native redux
makeRemoteRequest = () => {
const { page, seed } = state;
const url = `https://randomuser.me/api/?seed=${seed}&page=${page}&results=20`;
setState({ loading: true });
fetch(url)
.then(res => res.json())
.then(res => {
setState({
data: page === 1 ? res.results : [...state.data, ...res.results],
error: res.error || null,
loading: false,
refreshing: false
});
console.warn(res.results);
})
.catch(error => {
setState({ error, loading: false });
});
};
default:
return state;
}
};
export default peopleReducers;
friends.js
import React from "react";
import { View, Text, FlatList, ActivityIndicator } from "react-native";
import { ListItem, SearchBar } from "react-native-elements";
// connect to redux
import { connect } from "react-redux";
import { bindActionCreators } from "redux";
// import actions
import { addFriends } from "../config/redux/actions/peopleActions";
class friends extends React.Component {
componentDidMount() {
this.props.addFriends();
// this.makeRemoteRequest();
}
render() {
// console.warn(this.props.peopleListDashboard.data)
return (
<View>
<FlatList
data={this.props.peopleListDashboard.data}
renderItem={({ item }) => (
<ListItem leftAvatar={{ uri: item.picture.thumbnail }} />
)}
keyExtractor={item => item.email}
/>
</View>
);
}
}
// make accesible publicDashboard properties from reducer
const mapStateToProps = state => {
const { peopleListDashboard } = state;
return { peopleListDashboard };
};
const mapDispatchToProps = dispatch =>
bindActionCreators(
{
addFriends
},
dispatch
);
export default connect(
mapStateToProps,
mapDispatchToProps
)(friends);
about the trigger, yes I'm using action
peopleAction.js
import { ADD_FRIENDS } from "../types";
export const addFriends = friendsIndex => ({
type: ADD_FRIENDS,
payload: friendsIndex
});
hopefully you guys give me some clue, advice, even the answer is the good one for me. Thank You
I think you need to fetch your data in an action file, not in reducer, and then dispatch your response on the reducer and save it on the store. with this approach, you can call your action whenever you want and this must work for you.i suggest checking these links too :
https://redux.js.org/introduction/getting-started
https://stackoverflow.com/questions/43848257/where-to-put-business-logic-in-redux-action-or-store
this links maybe help you.
I am trying to figure out what is the best way to perform an api call. I need to get the data when the component loads and not on any onClick/onPress method.
This is the api call using fetch:
import { Alert, AsyncStorage } from 'react-native';
import { has } from 'lodash';
import PropTypes from 'prop-types';
import { compose } from 'redux';
import { connect } from 'react-redux';
import { passengersDataAction } from '../screens/HomeScreen/actions/homeScreen';
const GetPassengersData = async (
username,
password,
navigation,
passengersDataActionHandler,
) => {
try {
const response = await fetch(
'http://myAPI/public/api/getPassengers',
{
method: 'POST',
headers: {
Authorization: `Bearer ${868969}`,
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({ email: username, password }),
},
);
const responseJson = await response.json();
if (has(responseJson, 'error')) {
Alert.alert('Error', 'Please check your credentials.');
} else {
await AsyncStorage.setItem('userToken', responseJson.success.token);
passengersDataActionHandler(responseJson.success.token);
navigation.navigate('App');
}
} catch (error) {
Alert.alert(
'Error',
'There was an error with your request, please try again later.',
);
}
};
GetPassengersData.propTypes = {
navigation: PropTypes.shape({}).isRequired,
passengersDataActionHandler: PropTypes.func.isRequired,
};
export default compose(
connect(
store => ({
userToken: store.signinScreen.userToken,
passengersData: store.homeScreen.passengersData,
}),
dispatch => ({
passengersDataActionHandler: token => {
dispatch(passengersDataAction(token));
},
}),
),
)(GetPassengersData);
The call will be perform here:
class AllPassengers extends Component {
compo
render() {
return (
<ScrollView>
<View style={{ height: 50 }} />
<View
style={[
tabViewStyles.container,
{ alignItems: 'center', justifyContent: 'center' },
]}
>
<Text style={{ fontWeight: 'bold' }}>
Select passengers to start your route
</Text>
<View style={{ height: 50 }} />
<PassengersCircle />
</View>
<AllPassengersList />
</ScrollView>
);
}
}
export default AllPassengers;
And I was also wondering if there is a way to make that call on an functional/stateless component?
componentWillMount() is invoked just before mounting occurs. It is called right before render(), so by the time you get the data, the render() has already been called, your component should be able to render without any data.
It's recommended on the React Docs to fetch the data in componentDidMount().
Avoid introducing any side-effects or subscriptions in this method.
For those use cases, use componentDidMount() instead.
componentWillMount will be marked as “legacy”. it still works, but not recommended.
Note
This lifecycle was previously named componentWillMount. That name will continue to work until version 17. Use the
rename-unsafe-lifecycles codemod to automatically update your
components.
check this to see how to fetch data in a functional component.
Snippet from the link above :
import React, { useState, useEffect } from 'react';
import axios from 'axios';
function App() {
const [data, setData] = useState({ hits: [] });
useEffect(async () => {
const result = await axios(
'http://hn.algolia.com/api/v1/search?query=redux',
);
setData(result.data);
});
return (
<ul>
{data.hits.map(item => (
<li key={item.objectID}>
<a href={item.url}>{item.title}</a>
</li>
))}
</ul>
);
}
export default App;
componentWillMount has been considered "legacy" since React 16.3 and had "UNSAFE" prepended to the function name to make that point clear: https://reactjs.org/docs/react-component.html#unsafe_componentwillmount and it will be removed altogether in an upcoming release of React (17.0). You can read more about this in the React blog but the key quote is probably:
[componentWillMount] is problematic for both server rendering (where the external data won’t be used) and the upcoming async rendering mode (where the request might be initiated multiple times).
Therefore to keep your component working you should perform data fetching in componentDidMount.
To do this in a functional component, you may be able to use the new React feature of "Hooks". This enables the use of some state-mutating features of class components in functional components.