React/Redux application goes into an infinite loop on using useEffect with object references..
I am trying render pending todos for my application using useEffect.. and passing the array of todos as the second param in useEffect ..but why is not checking the values of the object ?
Container:
const mapDispatchToProps = dispatch => ({ actions: bindActionCreators(RootActions, dispatch) });
const Home = (props) => {
const { root, actions } = props;
useEffect(() => {
getTodos(actions.loadPendingTodo);
}, [root.data]);
return (
<Segment>
<Error {...root } />
<TodoList { ...root } actions={actions} />
</Segment>
);
};
export default connect(mapStateToProps, mapDispatchToProps)(Home);
Action:
export const loadPendingTodo = () => ({
type: LOAD_PENDING_TODO,
data: todoService.loadPendingTodo(),
});
Reducer:
const initialState = {
initial: true,
data: [{
id: 0,
text: 'temp todo',
dueDate: new Date(),
completedDate: '',
isDeleted: false,
isCompleted: false,
}],
error: false,
isLoading: false,
isEdit: false,
};
export default function root(state = initialState, action) {
switch (action.type) {
case LOAD_PENDING_TODO:
return {
...state,
data: [...action.data],
};
...
default:
return state;
}
}
getTodos Method:
export const getTodos = (loadTodo) => {
try {
loadTodo();
} catch (error) {
console.log(error); // eslint-disable-line
}
};
Service:
export default class TodoAppService {
loadPendingTodo() {
return store.get('todoApp').data.filter(todo => !todo.isCompleted && !todo.isDeleted);
}
Can anyone please help me out how to resolve this issue.. and there is no official documentation for this case too :/
Moreover changing the useEffect to the following works but i want to render on every change
useEffect(() => {
getTodos(actions.loadPendingTodo);
}, []);
Fixed it by removing the loadPedningTodo redux actions in useEffect that was causing it to loop and directly setting the data in function from service..
const Home = (props) => {
const { root, actions } = props;
return (
<Segment>
<Error {...root } />
<TodoList isEdit={root.isEdit} todo={todoService.loadPendingTodo()} actions={actions} />
</Segment>
);
};
thanks :)
Related
EDIT:
I fixed the problem in the reducer...changed this:
case ADD_LIST_ITEM:
return {
...state,
lists: {
...state.lists.map(list =>
list._id === payload.id
? { ...list, listItems: payload.data }
: list
)
},
loading: false
};
to this:
case ADD_LIST_ITEM:
return {
...state,
lists: [
...state.lists.map(list =>
list._id === payload.id
? { ...list, listItems: payload.data }
: list
)
],
loading: false
};
Stupid error on my part.
I have a MERN todo application using redux for state management and useEffect() for UI updates (all functional instead of class-based components). However, when I change state in the redux store, the UI does not update. This seems to only happen during an update triggered by a post request from the front end to the backend, where I pass data to an action, which is handled in a reducer (a js file rather than the useReducer() hook in this app). My backend will update properly, but the UI will crash.
What happens is, I input, say, a new list item in a given todo list, and the error I get is:
Uncaught TypeError: list.lists.map is not a function
at Dashboard (Dashboard.jsx:32)
I'm not sure where to use an additional useEffect(), if needed, or if there's a problem in my reducer...here's the relevant flow (removed all className declarations and irrelevant parts):
/* Dashboard.jsx */
// imports //
const Dashboard = ({ auth: { user }, list, getLists }) => {
useEffect(() => {
getLists();
}, [getLists]);
return (
<>
<p>Lists...</p>
{list.lists &&
list.lists.map(list => <List key={list._id} list={list} />)}
</>
);
};
Dashboard.propTypes = {
getLists: PropTypes.func.isRequired,
list: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
list: state.list
});
export default connect(mapStateToProps, { getLists })(Dashboard);
/* List.jsx */
// imports
const List = ({ list, addListItem, getLists }) => {
const [text, setText] = useState('');
useEffect(() => {
getLists();
}, []);
const handleAddItem = e => {
e.preventDefault();
addListItem(list._id, { text });
setText('');
};
return (
<div>
{list.listItems &&
list.listItems.map((item, index) => (
<ListItem
key={index}
item={item}
listId={list._id}
itemIndex={index}
/>
))}
<div>
<form onSubmit={handleAddItem}>
<input
type="text"
name="text"
placeholder="add a to-do item"
value={text}
onChange={e => setText(e.target.value)}
/>
<input type="submit" value="add" />
</form>
</div>
</div>
);
};
List.propTypes = {
addListItem: PropTypes.func.isRequired,
getLists: PropTypes.func.isRequired
};
export default connect(null, {
addListItem,
getLists
})(List);
/* list.actions.js */
// imports
export const addListItem = (listId, text) => async dispatch => {
try {
const res = await api.post(`/lists/${listId}`, text); // returns all list items after adding new item
dispatch({
type: ADD_LIST_ITEM,
payload: { id: listId, data: res.data }
});
} catch (err) {
dispatch({
type: LIST_ERROR,
payload: { message: err.response.statusText, status: err.response.status }
});
}
};
/* list.reducer.js */
// imports
const initialState = {
lists: [],
list: null,
loading: true,
error: {}
};
const list = (state = initialState, action) => {
const { type, payload } = action;
switch (type) {
case GET_LISTS:
return { ...state, lists: payload, loading: false };
case LIST_ERROR:
return { ...state, error: payload, loading: false };
case ADD_LIST_ITEM:
return {
...state,
lists: {
...state.lists.map(list =>
list._id === payload.id
? { ...list, listItems: payload.data }
: list
)
},
loading: false
};
default:
return state;
}
};
export default list;
I assume when creating your app's store, you are passing list as rootReducer,
Meaning your app's main state is exactly the state list is managing.
So if you need to access property lists of the state, you need to do it like this:
const mapStateToProps = state => ({
lists: state.lists /// state in here is exactly the state of list reducer
});
Now, in Dashboard lists is that array that you manipulate in list reducer.
Also, you have defined a property also named list in list reducer. It is initially defined to be null, also in the reducer, you never change it:
const initialState = {
lists: [],
list: null, /// none of actions ever change this, meaning it's currently useless.
loading: true,
error: {}
};
The component does not re render after successfully update state in redux
i have tried to do some condition in componentShouldUpdate end up with loading true without change
reducer.js
import * as types from "./actionsType";
const INITIAL_STATE = {
slide_data: [],
error: null,
loading: false,
};
const updateObject = (oldObject, updatedProperties) => {
return {
...oldObject,
...updatedProperties,
};
};
const slideReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case types.SLIDES_FETCH_START:
return updateObject(state, {
error: null,
loading: true,
});
case types.SLIDES_FETCH_SUCSSES:
return updateObject(state, {
slide_data: action.payload,
error: null,
loading: false,
});
case types.SLIDES_FETCH_FAIL:
return updateObject(state, {
error: action.error,
loading: false,
});
default:
return state;
}
};
export default slideReducer;
actions.js
import * as types from "./actionsType";
import axios from "axios";
import { selectSlides } from "./slides.selectors";
export const slidesStart = () => {
return {
type: types.SLIDES_FETCH_START,
};
};
export const slidesSucces = (slides) => {
return {
type: types.SLIDES_FETCH_SUCSSES,
payload: slides,
};
};
export const slidesFail = (error) => {
return {
type: types.SLIDES_FETCH_FAIL,
error: error,
};
};
export const fetchSlides = () => {
return (dispatch) => {
console.log("fetch Start");
dispatch(slidesStart());
axios
.get("http://127.0.0.1:8000/slides/", {
headers: {
"Content-Type": "application/json",
},
})
.then((res) => {
dispatch(slidesSucces(res.data));
})
.catch((err) => dispatch(slidesFail(err)));
};
};
component
class IntroPage extends Component {
constructor(props) {
super(props);
this.tlitRef = React.createRef();
this.titlelRef = React.createRef();
this.subTitleRef = React.createRef();
this.showcase = React.createRef();
}
componentDidMount() {
this.props.fetchSlides();
}
render() {
const { slides, loading } = this.props;
if (loading) {
return <h1>Loading</h1>;
}
return (
<div className="intro">
<div className="wrapper">
{slides.map((data) => (
<SwiperSlides data={data} key={data.name} />
))}
</div>
</div>
);
}
}
const mapStateToProps = (state) => {
return {
loading: state.slides.loading,
error: state.slides.error,
slides: state.slides.slide_data,
};
};
const mapDispatchToProps = (dispatch) => {
return {
fetchSlides: () => dispatch(fetchSlides()),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(IntroPage);
Register the redux-logger correctly. The data was returned but nothing changes when I do redux-persist and try to reload data come through
update ::
when I change the size of the browser data it correctly appears what is this !!
update : this problem related to swiperjs
and the solution will be like that:
1 - assign swiper instance to React.CreateRef(null) : this.swiper = React.CreateRef(null)
2 - in componentDidUpdate() make a swiper update : this.swiper.current.update()
4 - use a arrow function syntax in swiper on functions to refer to the outer scope
I've been trying to create this search app where I can display the items in a table and delete items using react redux. However, on the initial load, the app shows a table but there is no data in the table. It's an empty table. If i search for another movie name which have more than one movie for that search term, then 2 tables would be shown but I want to show everything on the same table itself. The delete button is not working as well. Is there something wrong with my action and reducer files?
Action.js
import {
FETCH_MOVIE_PENDING,
FETCH_MOVIE_SUCCESS,
FETCH_MOVIE_ERROR,
DELETE_MOVIE
} from "./types";
const fetchMoviePendig = () => ({
type: FETCH_MOVIE_PENDING
});
const fetchMovieSuccess = json => ({
type: FETCH_MOVIE_SUCCESS,
payload: json
});
const fetchMovieError = error => ({
type: FETCH_MOVIE_ERROR,
payload: error
});
export const fetchMovie = name => {
return async dispatch => {
dispatch(fetchMoviePendig());
try {
const url = `https://jsonmock.hackerrank.com/api/movies/search/?Title=${name}`;
const response = await fetch(url);
const result = await response.json(response);
console.log(result);
dispatch(fetchMovieSuccess(result.data));
} catch (error) {
dispatch(fetchMovieError(error));
}
};
};
export const deleteEvent = id => async dispatch => {
try {
dispatch({
type: DELETE_MOVIE,
payload: id
});
} catch (err) {
console.log(err);
}
};
Reducer
import {
FETCH_MOVIE_PENDING,
FETCH_MOVIE_SUCCESS,
FETCH_MOVIE_ERROR,
DELETE_MOVIE
} from "../action/types";
const initialState = {
data: [],
loading: false,
error: ""
};
const moviesReducer = (state = initialState, action) => {
switch (action.type) {
case FETCH_MOVIE_PENDING:
return {
...state,
loading: true
};
case FETCH_MOVIE_SUCCESS:
return {
...state,
loading: false,
data: [...state.data, action.payload]
};
case FETCH_MOVIE_ERROR:
return {
...state,
loading: false,
error: action.payload
};
case DELETE_MOVIE:
return {
...state,
data: state.data.filter(movie => movie.id !== action.payload)
};
default:
return state;
}
};
export default moviesReducer;
App.js
import React, { Component } from "react";
import { connect } from "react-redux";
import { fetchMovie } from "./action/movieActions";
import Input from "./components/Input";
import MovieTable from "./components/MovieTable";
class App extends Component {
state = {
searchInput: "The Rain"
};
componentDidMount() {
this.props.getMovieList(this.state.searchInput);
}
_getMovie = () => {
this.props.getMovieList(this.state.searchInput);
};
_onChangeHandler = e => {
this.setState({
searchInput: e.target.value
});
console.log(this.state.searchInput);
};
render() {
const { data, loading } = this.props.movies;
return (
<div className="center">
<div>
<h2 className="center white-text">Movie Search</h2>
</div>
<div className="container">
<Input
value={this.state.searchInput}
onChange={this._onChangeHandler}
onClick={this._getMovie}
/>
<div className="row">
{loading ? (
<p>Loading</p>
) : (
data.map(item => (
<MovieTable
key={item.imdbID}
year={item.Year}
name={item.Title}
movieId={item.imdbId}
/>
))
)}
</div>
</div>
</div>
);
}
}
const mapStateToProps = state => {
return {
movies: state.movies
};
};
const mapDispatchToProps = dispatch => {
return {
getMovieList: name => dispatch(fetchMovie(name))
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(App);
Hello please take a look at the sandbox : https://codesandbox.io/s/prod-wind-4hgq2?file=/src/App.js
I have edited
<MovieTable
data={data.map(d => ({
year: d.Year,
name: d.Title,
movieId: d.imdbId
}))}
/>
and
case FETCH_MOVIE_SUCCESS:
return {
...state,
loading: false,
data: action.payload
};
And ... Currently the delete button has no event, that's why it can't work
I see data having the following pattern:
Object {page: 1, per_page: 10, total: 1, total_pages: 1, data: Array[1]}
page: 1
per_page: 10
total: 1
total_pages: 1
data: Array[1]
0: Object
Title: "Sin in the Rain"
Year: 2006
imdbID: "tt1072449"
And you are accessing wrong properties in the component render logic, can you fix that.
Duplicate table is created the way you have written the logic.
Pass the data to MovieTable component and let it render and create the table
and fill it.
In reducer (FETCH_MOVIE_SUCCESS) you need don't need to append data you have to
replace or use the current movie data only.
Hey guys just moved to redux so in react what i was doing was in componentDidMount(), i was calling api and soon as i received the data i was setting loading to false (initially loading was true) to get rid of the 'react spinner',
but after using redux now in componentDidMount() i am calling my action creater which is in another and there i am receving my data so how do i manage 'loading' here ? can i somehow pass something from action creater to my component that triggers state and set loading to false ? or is there any other to do it ? How do you all manage it ?
here is my code
Home.js
class home extends Component {
UNSAFE_componentWillMount() {
this.props.verifyToken();
}
componentDidMount() {
this.props.categoryAction();
}
constructor(props) {
super(props);
this.state = {
categoriesWithTheirImages: [],
displayToggle: false,
loading: false,
};
}
renderCategory = () => {
return this.props.allCategories.map((item) => {
return (
<div
className="category_div"
key={item._id}
onClick={() => this.setState({ displayToggle: true })}
>
<img
src={item.image}
alt="miss-mistake"
className="category_image_home"
/>
<span className="category_heading_home">{item.categoryName}</span>
</div>
);
});
};
render() {
if (this.state.loading) {
return (
<div className="sweet-loading-main">
<FadeLoader
css={override}
sizeUnit={"px"}
size={50}
color={"#ff9d72"}
loading={this.state.loading}
/>
</div>
);
} else {
console.log(this.props.allCategories);
return (
<React.Fragment>
{/* <Fade left> */}
<Header />
<div className="main_content_homepage">
<p className="category_select">Please select a category</p>
<div className="category_list">{this.renderCategory()}</div>
</div>
{this.renderStoryActionDialog()}
{/* </Fade> */}
</React.Fragment>
);
}
}
}
const mapStateToProps = (state) => {
console.log(state);
const images = [family, ring, beer, feedback, academic];
let categoriesWithImages = state.getCategoryReducer.map((item, index) => {
item.image = images[index];
return item;
});
console.log(categoriesWithImages);
return { allCategories: categoriesWithImages };
};
export default connect(mapStateToProps, { verifyToken, categoryAction })(home);
and my action.js file
import { CATEGORY } from "../actionTypes";
export const categoryAction = ()=> {
return dispatch => {
fetch("http://localhost:3000/api/get_categories", {
method: "GET",
}).then(res=>res.json())
.then(response => {
console.log(response)
dispatch({ type: CATEGORY, payload: response });
})
.catch(err => console.log("Eror in adding", err));
};
};
reducer file
import { USER, CATEGORY} from "../actionTypes";
const getCategoryReducer = (state = [], action) => {
switch (action.type) {
case CATEGORY:
return action.payload;
default:
return state;
}
};
export default getCategoryReducer;
You should handle the loading state in your reducer file. At the moment, it's defined in your Component file. For e.g when you dispatch the action, it should update your loading state too. I would do something like this in reducer.
import { USER, FETCH_CATEGORY, FETCH_CATEGORY_SUCCESS, FETCH_CATEGORY_FAIL} from "../actionTypes";
const INITIAL_STATE = {
loading: false,
err: false,
data: []
}
const getCategoryReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case FETCH_CATEGORY:
return Object.assign({}, state, {
loading: true,
data: [],
})
case FETCH_CATEGORY_SUCCESS
return Object.assign({}, state, {
loading: false,
data: action.payload,
})
case FETCH_CATEGORY_FAIL
return Object.assign({}, state, {
loading: false,
data: action.payload,
err: true
})
default:
return state;
}
};
export default getCategoryReducer;
and your action file would look something like this
import { FETCH_CATEGORY, FETCH_CATEGORY_SUCCESS, FETCH_CATEGORY_FAIL } from "../actionTypes";
export const categoryAction = ()=> {
//setting loading to true
return dispatch => {
dispatch({ type: FETCH_CATEGORY });
fetch("http://localhost:3000/api/get_categories", {
method: "GET",
}).then(res=>res.json())
.then(response => {
//setting loading to false
dispatch({ type: FETCH_CATEGORY_SUCCESS, payload: response });
})
.catch(err => console.log("Eror in adding", err); dispatch({ type: FETCH_CATEGORY_FAIL, payload: err }););
};
};
You can then read the loading props in your Home.js
I have setup the redux store but when I try to make changes to the state using mapStateToProps and mapDispatchToProps I get always the default state. So at account.js I want to get the selected language and then add it to the redux store. I try to call it in other components but I always end up with reducers/Language.js defaultState. What I'm doing wrong?
Account.js
class Account extends React.Component {
static navigationOptions = ({ navigation }) => {};
constructor(props) {
super(props);
this.state = {
language: {
sq: true,
en: false,
selected: '',
},
};
}
changeLanguage = (selected) => {
if (this.state.sq) {
this.setState({ selected: 'sq' });
} else {
this.setState({ selected: 'en' });
}
};
render() {
const navigation = this.props.navigation;
return (
<ScrollView>
<View>
<ThemeProvider>
<TableView header={I18n.t('account.lang_label')}>
<CheckboxRow
selected={this.state.language.sq}
onPress={() => {
this.setState(state => ({
language: {
sq: !state.language.sq,
en: !state.language.en,
},
}));
this.changeLanguage();
}}
title={I18n.t('account.albanian')}
/>
<CheckboxRow
selected={this.state.language.en}
onPress={() =>
this.setState(state => ({
language: {
en: !state.language.en,
sq: !state.language.sq,
},
}))
}
title={I18n.t('account.english')}
/>
</TableView>
</ThemeProvider>
</View>
</ScrollView>
);
}
}
const mapDispatchToProps = dispatch => {
return {
changeLanguage: (selected) => { dispatch(changeLanguageEn(selected))},
};
};
const mapStateToProps = state => {
return {
language: state.language.selected,
};
};
export default withNavigation(connect(
mapStateToProps,
mapDispatchToProps
)(Account));
actions/Language.js
import {CHANGE_LANGUAGE_EN, CHANGE_LANGUAGE_AL} from "./types";
export const changeLanguageEn = (language) => {
return {
type: CHANGE_LANGUAGE_EN,
lang: language,
}
};
export const changeLanguageAl = (language) => {
return {
type: CHANGE_LANGUAGE_AL,
lang: language,
}
};
reducers/Language.js
const defaultState = {
lang: '',
};
export default function reducer(state = defaultState, action) {
switch (action.type) {
case 'CHANGE_LANGUAGE_EN':
return {...state, lang: 'en'};
case 'CHANGE_LANGUAGE_AL':
return Object.assign({}, state, {
lang: 'sq',
});
default:
return state;
}
}
In your mapStateToProps function try with state.lang directly
const mapStateToProps = state => {
return {
language: state.lang,
};
};
Hope this will work.
Not entirely sure I understand your question, but it sounds like you're trying to update the redux state with the internal state of Account? You should be able to do:
this.props.changeLanguage(this.state.language.selected)
You have a method on your component defined changeLanguage as well, perhaps you could do the line above in that method, after changing the internal state
additionally, in your changeLanguage method in your Account class, I don't think this.state.sq exists since sq is a key in the language state object. Instead it should be this.state.language.sq. You don't need to add the selected argument to this method either. Try making your changeLanguage method to look like this
changeLanguage = () => {
if (this.state.sq) {
this.setState({ language.selected: 'sq' });
} else {
this.setState({ language.selected: 'en' });
}
// dispatch your action here after updating the state
this.props.changeLanguage(this.state.language.selected)
};
Now calling this.changeLanguage(); will update your internal state, and then dispatch your changeLanguage redux action
You are accessing the selected language incorrectly. state.language.selected.
In the reducer you are adding lang property in the state, so access it with the same property name in the mapStateToProps.
const mapStateToProps = state => {
return {
language: state.language.lang,
};
};