I'm learning redux and i'm making a sort of pokedex app where i fetch 20 pokemons from pokeapi.co at a time. When the page changes a new list of 20 pokemons is fetched. The problem is that while state changes to the new pokemons, they don't actually render.
App.js
const App = () => {
const dispatch = useDispatch();
const offset = useSelector(state => state.offset);
const limit = useSelector(state => state.limit);
useEffect(() => {
//FETCHES 20 OBJECTS THAT CONTAIN AN URL TO AN INDIVIDUAL POKEMON
dispatch(fetchPokemons(limit, offset));
}, [limit, offset, dispatch]);
...
return (
<div style={{ backgroundColor: '#222222' }}>
<Notification />
<AppBarPokemon />
<Switch>
<Route path="/pokemons">
<PokemonsDisplay CapsFirstLetter={CapsFirstLetter}/>
</Route>
...
PokemonsDisplay.js
const PokemonsDisplay = ({ CapsFirstLetter }) => {
const dispatch = useDispatch();
const classes = useStyles();
const pokemons = useSelector(state => state.pokemons);
console.log(pokemons);
const pageSize = 20;
const totalCount = 898;
const handleClick = (p) => {
dispatch(getOnePokemon(p));
};
return (
<div className={classes.root}>
{pokemons && (
<Grid container spacing={3}>
{pokemons.map(p => (
<Grid item xs={3} key={p.name} className={classes.gridItem} component={Link} onClick={() => handleClick(p)} to={`/pokemons/${p.name}`} data-cy={`pokemon-button-${p.name}`}>
<Paper className={classes.paper && classes.color} elevation={10}>
<p className={classes.p}>#{p.id}</p>
<p className={classes.p}>{CapsFirstLetter(p.name)}</p>
{p.sprites.other["dream_world"]["front_default"] !== null ?
<img className={classes.image} alt={`${p.name}'s sprite`} src={p.sprites.other["dream_world"]["front_default"]}/> :
<img className={classes.image} alt={`${p.name}'s sprite`} src={p.sprites.other["official-artwork"]["front_default"]}/>}
</Paper>
</Grid>
))}
</Grid>
)}
<Pagination
totalCount={totalCount}
pageSize={pageSize}
/>
</div>
);
};
pokemonsReducer.js
import getPokemons from '../services/pokemons';
import axios from 'axios';
import { loadPokemonsFromLS, savePokemonsList } from '../utils/localStoragePokemons';
const pokemonsReducer = (state = [], action) => {
console.log('state is:', state)
switch(action.type){
case 'INIT_POKEMONS':
return action.data;
default:
return state;
};
};
export const fetchPokemons = (limit, offset) => {
return async dispatch => {
try {
const pokemons = loadPokemonsFromLS(limit, offset);
dispatch({
type: 'INIT_POKEMONS',
data: pokemons
});
} catch (error) {
const pokemons = await getPokemons.getPokemons(limit, offset);
let pokemonsArray = [];
let pokemonsObject = {};
pokemons.results.forEach(async (r, i) => {
//FETCHES EACH POKEMON URL AND STORES ITS DATA ON pokemons STATE
const pokemonNow = await axios.get(r.url);
pokemonsArray.push(pokemonNow.data);
pokemonsObject[i] = pokemonNow.data
});
savePokemonsList(limit, offset, pokemonsObject);
dispatch({
type: 'INIT_POKEMONS',
data: pokemonsArray
});
}
};
};
export default pokemonsReducer;
I have tried to dispatch({ data: [...pokemons] })
But it doesnt work.
Also i forgot to add. When i go to my component that is routed to '/' and then back to '/pokemons' they render.
Edit: I think i'm getting there.
i changed the reducer function so that it gets called independently from the dispatch, the problem is that now the action doesn't get fired xD.
export const fetchEverything = async (limit, offset) => {
try {
const pokemons = loadPokemonsFromLS(limit, offset);
initPokemons(pokemons);
} catch (error) {
const pokemonsData = await getPokemons.getPokemons(limit, offset);
let pokemons = [];
let pokemonsObject = {};
console.log(pokemonsData)
pokemonsData.results.forEach(async (r, i) => {
//FETCHES EACH POKEMON URL AND STORES ITS DATA ON pokemons STATE
const pokemonNow = await axios.get(r.url);
pokemonsObject[i] = pokemonNow.data;
//console.log([pokemonNow.data][0]);
pokemons.push(pokemonNow.data);
});
console.log(pokemons);
console.log(pokemonsObject);
savePokemonsList(limit, offset, pokemonsObject);
initPokemons(pokemons);
};
};
export const initPokemons = (pokemons) => {
return dispatch => dispatch({ type: 'INIT_POKEMONS', pokemons: pokemons })
};
const pokemonsReducer = (state = [], action) => {
switch(action.type){
case 'INIT_POKEMONS':
console.log(action);
const newState = action.pokemons
return newState;
default:
return state;
};
};
It happens due to redux state mutation, you can resolve this issue using immer as stated in redux documentation as well. https://www.npmjs.com/package/immer
Related
Hook and then map is my possible solution but i need to know how
Well I'm using react and firestore so, the data fetch is saved in a hook called "Asistencias"
well my target it was to get a group of weeks with the same number and collect data by that week, n i get it, but now i would like to render that data, so I need help.
this is the response with the function with reduce
export const Presupuesto = () => {
const auth = getAuth()
const dato =auth.currentUser;
const [Presupuestos, setPresupuesto] = useState([]);
const [Asistencias, setAsistencias] = useState([]);
const [itinerante, setItinerante] = useState([])
const getPresupuestos =async () => {
const q = query(collection(db, "asignaciones"),where("asistencias", "!=", [] ))
await onSnapshot(q, (query)=>{
const data=[]
query.forEach((doc)=>{
data.push(doc.data())
})
setPresupuesto(data)
}) }
useEffect(()=>{
getPresupuestos()
},[])
console.log("hook: ", Asistencias);
const AsistenciasPresupuesto = (props) => {
return props.reduce((past, current)=>{
const foundItem = past.find(it => it.semana === current.semana)
console.log('past:', past);
if (foundItem ){
foundItem.data=foundItem.data
?[...foundItem.data, {'trabajador': current.trabajador,'entrada':current.entrada, 'salida': current.salida}]
:[{ 'trabajador': current.trabajador,'entrada':current.entrada, 'salida': current.salida }]
}else{ past.push( {
'semana': current.semana,
'data': [{
'trabajador': current.trabajador,'entrada':current.entrada, 'salida': current.salida
}]
} ) }
return past;
}, [])}
AsistenciasPresupuesto(Asistencias)
return (
<Card>
<div className='presupuestos'>
{
Presupuestos.map((presupuesto)=>(
<Button variant="danger"
id={presupuesto.obra}
className={presupuesto.obra}
value={presupuesto.presupuesto}
onClick={
(e)=>{
e.preventDefault()
console.log("objeto completo:", presupuesto.asistencias)
setAsistencias(presupuesto.asistencias)
console.log("asistencias:", Asistencias)
}} > {presupuesto.presupuesto} </Button>))
}
</div>
<div>
<Card id="prueba" className='lg'>
{/*
i would like to render here! */}
</Card>
</div>
</Card>
)
}
this is my code
this is the render
I want to get a list of churches from the store at initialization but i can't. The log get my initial array and the new one but doesn't display. Log below:
log
here is my model:
const churchModel = {
items: [],
// ACTIONS
setAllChurches: action((state, payload) => {
state.items = payload;
}),
getInitialChurches: thunk(async (actions) => {
const { data } = await axios.post(
'http://localhost:3000/api/geo/closeto?latlong=2.3522219 48.856614&distance=10000'
);
let array = [];
const resData = data.map(async (index) => {
const res = await axios.get(`http://localhost:3000/api/institutions/all?idInstitution=${index.idInstitution}`);
array.push(res.data[0]);
});
actions.setAllChurches(array);
})
}
and my component:
const ChurchList = () => {
const classes = useStyles();
const setInitialChurches = useStoreActions(action => action.churches.getInitialChurches);
const churches = useStoreState(state => state.churches.items);
const [activeItem, setActiveItem] = React.useState(null);
useEffect(() => {
setInitialChurches()
}, []);
return (
<div className={classes.root} style={{marginTop: '20px',}}>
{ churches.map( (church) => (
<ChurchItem
key={ church.idInstitution }
church={ church }
setActiveItem={setActiveItem}
activeItem={activeItem}
/>)
), console.log(churches)}
</div>
)
};
export default ChurchList;
I tried a useEffect but nothing true. Could you help me please ?
that is not a good location to put console.log in, either put it outside the component render, inside the map or on a useEffect.
You can achieve it by using useEffect and passing churches on the array.
useEffect(() => {
// this will log everytime churches changes / initialized churches
console.log(churches);
}, [churches]);
I need to render a component that has a route using react router. the first component has a button that when clicked needs to render another component that has state passed in from the first component. The page redirects but doesn't load. All of the data from the first component I want is passed in but it wont set state when I use setProfile(p). All the other console.log()s in the member component show all the data I expect but it won't set the state with this data.
import {useLocation} from "react-router-dom";
const Member = (props)=> {
const [user, setUser] = useState({});
const [profile, setProfile] = useState({});
const [user, setUser] = useState({});
const { state } = useLocation();
const [profile, setProfile] = useState({});
const dispatch = useDispatch();
const [list, setList] = useState([]);
const [posts, setPosts] = useState([]);
const [snInstance, setsnInstance] = useState({});
// run effect when user state updates
useEffect(() => {
const doEffects = async () => {
try {
// const p = await incidentsInstance.usersProfile(state.user, { from: accounts[0] });
// const a = await snInstance.getUsersPosts(state.user, { from: accounts[0] });
if (state && state.user) {
setUser(state.user);
}
const accounts = await MyWeb3.getInstance().getAccounts();
setAccounts(accounts);
console.log(accounts)
const incidents = MyWeb3.getInstance().getContract(Incidents)
const incidentsInstance = await MyWeb3.getInstance().deployContract(incidents);
const sn = MyWeb3.getInstance().getContract(SocialNet)
const snInstance = await MyWeb3.getInstance().deployContract(sn);
setsnInstance(snInstance);
const pro = socialNetworkContract.members[0]
console.log(pro)
const p = await incidentsInstance.usersProfile(pro, { from: accounts[0] });
const a = await snInstance.getUsersPosts(pro, { from: accounts[0] });
console.log(a)
console.log(p)
setProfile(p)
} catch (e) {
console.error(e)
}
}
doEffects();
}, [profile, state]);
const socialNetworkContract = useSelector((state) => state.socialNetworkContract)
return (
<div class="container">
<a target="_blank">Name : {profile.name}</a>
{socialNetworkContract.posts.map((p, index) => {
return <tr key={index}>
{p.message}
</tr>})}
</div>
)
}
export default Member;
This is the parent component I want to redirect from
const getProfile = async (member) => {
const addr = dispatch({ type: 'ADD_MEMBER', response: member })
console.log(member)
}
const socialNetworkContract = useSelector((state) => state.socialNetworkContract)
return (
<div>
{socialNetworkContract.posts.map((p, index) => {
return <tr key={index}>
<button onClick={() => getProfile(p.publisher)}>Profile</button>
</tr>})}
</div>
)
}
export default withRouter(Posts);
I have this component working when I don't have a dynamic route that needs data passing in from the parent component It's redirecting from.
My routes.js looks like
const Routes = (props) => {
return (
<Switch>
<Route path="/member" exact component={Member} />
<Route path="/posts" exact component={Posts} />
<Redirect exact to="/" />
</Switch>
)
}
export default Routes
This is the reducer
import { connect, useDispatch, useSelector } from "react-redux";
let init = {
posts:[],
post:{},
profiles:[],
profile:{},
members:[],
member:{}
}
export const socialNetworkContract = (state = init, action) => {
const { type, response } = action;
switch (type) {
case 'ADD_POST':
return {
...state,
posts: [...state.posts, response]
}
case 'SET_POST':
return {
...state,
post: response
}
case 'ADD_PROFILE':
return {
...state,
profiles: [...state.profiles, response]
}
case 'SET_PROFILE':
return {
...state,
profile: response
}
case 'ADD_MEMBER':
return {
...state,
members: [...state.members, response]
}
case 'SET_MEMBER':
return {
...state,
member: response
}
default: return state
}
};
It doesn't make any sense that you would dispatch({ type: 'ADD_MEMBER', response: member }) with a member object that came from the publisher property of a post. That info is already in your state. You probably need to be normalizing your state better so that you can select it where you need it.
You want to use the Link component from react-router-dom to navigate to a member's profile page. Your Route should render the correct profile based on an id or username property in the URL. Don't pass through the data when you redirect, just go to the correct URL. On that Member page you can get the user from the state by looking up the id.
In Posts:
<Link to={`/member/${p.publisher.id}`}><button>Profile</button></Link>
In Routes:
<Route path="/member/:id" component={Member} />
In Member:
const Member = () => {
const { id } = useParams();
const profile = useSelector((state) =>
state.socialNetworkContract.members.find((user) => user.id === id)
);
const dispatch = useDispatch();
useEffect(() => {
const doEffects = async () => {
if ( ! profile ) {
dispatch(loadUser(id));
}
};
doEffects();
}, [dispatch, profile, id]);
Not sure if this is a so-called "pub/sub" pattern or a form of a "pub/sub" pattern. I am trying to create a piece of shared state so that different components can subscribe to it and only gets updated when there is an update with that state.
const useForceUpdate = () => useReducer((state) => !state, false)[1];
const createSharedState = (reducer, initialState) => {
const subscribers = [];
let state = initialState;
const dispatch = (action) => {
state = reducer(state, action);
subscribers.forEach((callback) => callback());
};
const useSharedState = () => {
const forceUpdate = useForceUpdate();
useEffect(() => {
const callback = () => forceUpdate();
subscribers.push(callback);
const cleanup = () => {
const index = subscribers.indexOf(callback);
subscribers.splice(index, 1);
};
return cleanup;
}, []);
return [state, dispatch];
};
return useSharedState;
};
const initialState = 0;
const reducer = (state, action) => {
switch (action.type) {
case "increment":
return state + 1;
case "decrement":
return state - 1;
case "set":
return action.count;
default:
return state;
}
};
const useCount1 = createSharedState(reducer, initialState);
const useCount2 = createSharedState(reducer, initialState);
const Counter = ({ count, dispatch }) => (
<div>
{count}
<button onClick={() => dispatch({ type: "increment" })}>+1</button>
<button onClick={() => dispatch({ type: "decrement" })}>-1</button>
<button onClick={() => dispatch({ type: "set", count: 0 })}>reset</button>
</div>
);
const Counter1 = () => {
const [count, dispatch] = useCount1();
return <Counter count={count} dispatch={dispatch} />;
};
const Counter2 = () => {
const [count, dispatch] = useCount2();
return <Counter count={count} dispatch={dispatch} />;
};
const Example = () => (
<>
<Counter1 />
<Counter1 />
<Counter2 />
<Counter2 />
</>
);
<script src="https://unpkg.com/#babel/standalone#7/babel.min.js"></script>
<script src="https://unpkg.com/react#17/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom#17/umd/react-dom.production.min.js"></script>
<div id="root"></div>
<script type="text/babel">
const { useEffect, useReducer } = React;
const useForceUpdate = () => useReducer((state) => !state, false)[1];
const createSharedState = (reducer, initialState) => {
const subscribers = [];
let state = initialState;
const dispatch = (action) => {
state = reducer(state, action);
subscribers.forEach((callback) => callback());
};
const useSharedState = () => {
const forceUpdate = useForceUpdate();
useEffect(() => {
const callback = () => forceUpdate();
subscribers.push(callback);
const cleanup = () => {
const index = subscribers.indexOf(callback);
subscribers.splice(index, 1);
};
return cleanup;
}, []);
return [state, dispatch];
};
return useSharedState;
};
const initialState = 0;
const reducer = (state, action) => {
switch (action.type) {
case "increment":
return state + 1;
case "decrement":
return state - 1;
case "set":
return action.count;
default:
return state;
}
};
const useCount1 = createSharedState(reducer, initialState);
const useCount2 = createSharedState(reducer, initialState);
const Counter = ({ count, dispatch }) => (
<div>
{count}
<button onClick={() => dispatch({ type: "increment" })}>+1</button>
<button onClick={() => dispatch({ type: "decrement" })}>-1</button>
<button onClick={() => dispatch({ type: "set", count: 0 })}>reset</button>
</div>
);
const Counter1 = () => {
const [count, dispatch] = useCount1();
return <Counter count={count} dispatch={dispatch} />;
};
const Counter2 = () => {
const [count, dispatch] = useCount2();
return <Counter count={count} dispatch={dispatch} />;
};
const Example = () => (
<>
<Counter1 />
<Counter1 />
<Counter2 />
<Counter2 />
</>
);
ReactDOM.render(<Example />, document.querySelector("#root"));
</script>
It seems to be working fine. My questions are:
Is this a valid way to implement shared update subscription?
Is there any drawbacks with using a simple variable to hold the state + forcing React to re-render if that piece of state changes, instead of using useState or useReducer as one normally would do?
any feedback is welcomed.
Your idea is excellent. React team was also thinking on this topic and ended up with the creation of https://recoiljs.org/. You can use it as useState (DEMO) or as useReducer (DEMO).
I don't want to highlight your solution's drawbacks. Instead, I'd like to list the advantages of using Recoil:
Internal memory usage optimization.
No need to support the code (Facebook does it).
No cheating (useForceUpdate).
Supports selectors out of the box.
I'd recommend you to learn more about Recoil and start using it because it gives the exact result you want to achieve.
I am making this web applications which has posts where users can put answers to those posts. I have used React-Redux to manage the state of the application. Every time I create or update an answer of a particular post the whole list of answers which belongs to that post gets re-rendered and I want to stop that and render only the newly created or updated one. I have used exactly the same way for post comments and it works fine. Comments doesn't get re-rendered but answers does. I just can't figure out what is the problem here. Please refer the code below.
I tried using React.memo() also and it doesn't work either!
Answer render component,
export function Answer() {
const classes = useStyles();
const dispatch = useDispatch();
const { postId } = useParams();
const postAnswers = useSelector(state => state.Answers);
const [answers, setAnswers] = React.useState(postAnswers.answers);
React.useEffect(() => {
if(postAnswers.status === 'idle') dispatch(fetchAnswers(postId));
}, [dispatch]);
React.useEffect(() => {
if(postAnswers.answers) handleAnswers(postAnswers.answers);
}, [postAnswers]);
const handleAnswers = (answers) => {
setAnswers(answers);
};
const AnswersList = answers ? answers.map(item => {
const displayContent = item.answerContent;
return(
<Grid item key={item.id}>
<Grid container direction="column">
<Grid item>
<Paper component="form" className={classes.root} elevation={0} variant="outlined" >
<div className={classes.input}>
<Typography>{displayContent}</Typography>
</div>
</Paper>
</Grid>
</Grid>
</Grid>
);
}): undefined;
return(
<Grid container direction="column" spacing={2}>
<Grid item>
<Divider/>
</Grid>
<Grid item>
<Grid container direction="column" alignItems="flex-start" justify="center" spacing={2}>
{AnswersList}
</Grid>
</Grid>
<Grid item>
<Divider/>
</Grid>
</Grid>
);
}
Fetch answers redux apply,
export const fetchAnswers = (postId) => (dispatch) => {
dispatch(answersLoading());
axios.get(baseUrl + `/answer_api/?postBelong=${postId}`)
.then(answers =>
dispatch(addAnswers(answers.data))
)
.catch(error => {
console.log(error);
dispatch(answersFailed(error));
});
}
Post answers,
export const postAnswer = (data) => (dispatch) => {
axios.post(baseUrl + `/answer_api/answer/create/`,
data
)
.then(response => {
console.log(response);
dispatch(fetchAnswers(postBelong)); //This is the way that I update answers state every time a new answer is created or updated
})
.catch(error => {
console.log(error);
});
}
Any help would be great. Thank you!
After adding an item you fetch all the items from the api so all items are recreated in the state. If you give a container component the id of the item and have the selector get the item as JSON then parse back to object you can memoize it and prevent re render but I think it's probably better to just re render.
Here is an example of memoized JSON for the item:
const { Provider, useDispatch, useSelector } = ReactRedux;
const { createStore, applyMiddleware, compose } = Redux;
const { createSelector } = Reselect;
const fakeApi = (() => {
const id = ((num) => () => ++num)(1);
const items = [{ id: 1 }];
const addItem = () =>
Promise.resolve().then(() =>
items.push({
id: id(),
})
);
const updateFirst = () =>
Promise.resolve().then(() => {
items[0] = { ...items[0], updated: id() };
});
const getItems = () =>
//this is what getting all the items from api
// would do, it re creates all the items
Promise.resolve(JSON.parse(JSON.stringify(items)));
return {
addItem,
getItems,
updateFirst,
};
})();
const initialState = {
items: [],
};
//action types
const GET_ITEMS_SUCCESS = 'GET_ITEMS_SUCCESS';
//action creators
const getItemsSuccess = (items) => ({
type: GET_ITEMS_SUCCESS,
payload: items,
});
const getItems = () => (dispatch) =>
fakeApi
.getItems()
.then((items) => dispatch(getItemsSuccess(items)));
const update = () => (dispatch) =>
fakeApi.updateFirst().then(() => getItems()(dispatch));
const addItem = () => (dispatch) =>
fakeApi.addItem().then(() => getItems()(dispatch));
const reducer = (state, { type, payload }) => {
if (type === GET_ITEMS_SUCCESS) {
return { ...state, items: payload };
}
return state;
};
//selectors
const selectItems = (state) => state.items;
const selectItemById = createSelector(
[selectItems, (_, id) => id],
(items, id) => items.find((item) => item.id === id)
);
const createSelectItemAsJSON = (id) =>
createSelector(
[(state) => selectItemById(state, id)],
//return the item as primitive (string)
(item) => JSON.stringify(item)
);
const createSelectItemById = (id) =>
createSelector(
[createSelectItemAsJSON(id)],
//return the json item as object
(item) => JSON.parse(item)
);
//creating store with redux dev tools
const composeEnhancers =
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
reducer,
initialState,
composeEnhancers(
applyMiddleware(
({ dispatch, getState }) => (next) => (action) =>
//simple thunk implementation
typeof action === 'function'
? action(dispatch, getState)
: next(action)
)
)
);
const Item = React.memo(function Item({ item }) {
const rendered = React.useRef(0);
rendered.current++;
return (
<li>
rendered:{rendered.current} times, item:{' '}
{JSON.stringify(item)}
</li>
);
});
const ItemContainer = ({ id }) => {
const selectItem = React.useMemo(
() => createSelectItemById(id),
[id]
);
const item = useSelector(selectItem);
return <Item item={item} />;
};
const ItemList = () => {
const items = useSelector(selectItems);
return (
<ul>
{items.map(({ id }) => (
<ItemContainer key={id} id={id} />
))}
</ul>
);
};
const App = () => {
const dispatch = useDispatch();
React.useEffect(() => dispatch(getItems()), [dispatch]);
return (
<div>
<button onClick={() => dispatch(addItem())}>
add item
</button>
<button onClick={() => dispatch(update())}>
update first item
</button>
<ItemList />
</div>
);
};
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.2.0/react-redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/reselect/4.0.0/reselect.min.js"></script>
<div id="root"></div>
I just found where the problem was which led to the above question.
In my state management system there is an action named answers to handle the state of post answers like below.
import * as ActionTypes from '../ActionTypes';
export const Answers = (state = {
status: 'idle',
errMess: null,
answers: []
}, action) => {
switch(action.type) {
case ActionTypes.ADD_ANSWER_LIST:
return {...state, status: 'succeeded', errMess: null, answers: action.payload}
case ActionTypes.ANSWER_LIST_LOADING:
return {...state, status: 'loading', errMess: null, answers: []}
case ActionTypes.ANSWER_LIST_FAILED:
return {...state, status: 'failed', errMess: action.payload, answers: []}
default:
return state;
}
}
The problem here is that the empty arrays that I have put in ANSWER_LIST_LOADING and ANSWER_LIST_FAILED cases. Every time the action creator fetches new data, it goes through the loading state and there it gets an empty array which leads the whole list of answers to be re-rendered and re-created unnecessarily. So I changed the implementation as follows and it fixed the problem.
export const Answers = (state = {
status: 'idle',
errMess: null,
answers: []
}, action) => {
switch(action.type) {
case ActionTypes.ADD_ANSWER_LIST:
return {...state, status: 'succeeded', errMess: null, answers: action.payload}
case ActionTypes.ANSWER_LIST_LOADING:
return {...state, status: 'loading', errMess: null, answers: [...state.answers]}
case ActionTypes.ANSWER_LIST_FAILED:
return {...state, status: 'failed', errMess: action.payload, answers: [...state.answers]}
default:
return state;
}
}
All the time the problem has been in a place where I never thought it would be. I haven't even mentioned about this action in my question. But there you go.