I have a component that upon componentDidMount will perform an async API database fetch.
On the same component the user can add an item to the collection. Unfortunately, the component does not automatically re-render, even though the new document was added to the collection. How do I invoke another render with the new document showing as well?
If your collection is an array, when an item was created you should return a new array by using concat method instead of push or access directly into the array via the index.
See this code:
const myReducer = (state = initState, action) => {
switch (action.type) {
case 'FETCH_SUCCESSFUL': return action.data
case 'NEW_ITEM': return [].concat(state, [action.newItem])
}
}
When you return a new array, redux will know the state was changed and your component will also update automatically.
Related
I am creating a job list. In this job list, we have a button to open modal for enter the detail of a job. However, when I pass a default job object with default value to the modal, the modal cannot show the default job value.
Because some default attributes are came from remote API, so in the JobList component, I use the useEffect hook to retrieve data from remote API.
Would you tell me what's going on?
My code is resided here.
I checked your code and there is a logical error.
You need to add break; after addJob case statement in JobList.js.
Because of that, defaultJobObj is reiniting as undefined object.
-- ADDITION --
You need to handle defaultJobObj prop update in JobForm component.
You can define a hook and reinit items in reducer.
let reducer = (state, action) => {
let result = { ...state };
switch (action.type) {
case "reinit":
result.job = defaultJobObj;
break;
default:
break;
}
return result;
};
useEffect(() => {
updateItems({
"type":"reinit"
})
}, [defaultJobObj])
Maybe you can use state management like Redux/React Context to pass the values to your modal
In my React project, I have implemented memoization using reselect library.
The state basically has a list of objects which I render as cards.
Before implementing reselect, whenever I added a new element, the change instantly showed up and a new card got added at the end. However, now when I add a new element it does not instantly shows up, but rather shows up when the page is reloaded.
Why does this happen? And is there a way to fix this without removing the use of reselect library
EDIT : The issue has been solved, and as pointed out in the answers it was because I was simply mutating the state
The earlier code was as follows
case IssueActionTypes.ADD_ISSUE:
state.issueList.push(action.payload)
return {
...state
}
which I replaced with
case IssueActionTypes.ADD_ISSUE:
return {
...state,
issueList : [...state.issueList, action.payload]
}
which fixed the issue
Most likely you are returning mutated state in your reducers instead of returning a new array.
Docs:
createSelector uses an identity check (===) to detect that an input
has changed, so mutating an existing object will not trigger the
selector to recompute because mutating an object does not change its
identity. Note that if you are using Redux, mutating the state object
is almost certainly a mistake.
Example of returning mutated state (from docs):
export default function todos(state = initialState, action) {
switch (action.type) {
case COMPLETE_ALL:
const areAllMarked = state.every(todo => todo.completed)
// BAD: mutating an existing object
return state.map(todo => {
todo.completed = !areAllMarked
return todo
})
default:
return state
}
}
I have React app with Redux that has following structure:
<ComponentParent>
<ComponentA></ComponentA>
<ComponentB></ComponentB>
</ComponentParent>
In component A an ComponentDidMount, a fetch is called and data is return async-ly. Reducer is then called to add data to the store.
Component B then accesses the store to access data added by A to the store.
Predictably Component B accesses the data before Component A had a change to write data to the store (because data is coming from aync fetch).
Question:
what is a proper way to design such interaction?
Do I need use
approach similar to
react redux with asynchronous fetch
? Note that in Reducer I just store data returned async-ly by
Component A, unlike in the link
Thanks
Set a default state to your componentB for it to load while awaiting results from your fetch.
In your fetch action, assuming you use redux-thunk:
let fetchData = () => async dispatch => {
let res = await fetchFromDataSource();
dispatch({
type: UPDATE_STATE,
payload: res
})
};
Your component B should be linked up to the store. Upon dispatch update, it should trigger your componentB to reload via ComponentDidUpdate.
I like the pattern of creating an initial state for the object in the reducer, so that any component accessing it gets that initial state first, and can later update based on a post-fetch state.
xReducer.js
const initState = {
// Initial state of object
};
export default function xReducer(state=initState, action) {
switch (action.type) {
case actionTypes.MY_POST_FETCH_ACTION_TYPE:
return {
...state,
// state override
};
default:
return state;
}
}
I am using redux to move search filter through application to components.
I have component where I set up filters and call dispatch function using
this.props.dispatch(setFilter(newFilter));
I use sliders / range components to set up values, those components have handleAfterChange method which is set up to call mentioned dispatch function. This is working fine.
I have also filters that are set up by clicking buttons, I created onClick handler, and this handler call mentioned function. I checked what I am sending to setFilter functions (newFilter) parameter and it is what I want to set up.
My set action is defined:
export const setFilter = (filter = {}) => {
return {
type: SET_FILTER,
filter
};
};
My reducer is:
const searchFilter = (prevState = INITIAL_STATE.filter, action) => {
switch (action.type) {
case SET_FILTER: {
// prevState is already the state
// I want to set up in this reducer
console.log(prevState);
return Object.assign({}, prevState, action.filter);
}
case RESET_FILTER: {
return INITIAL_STATE.filter;
}
default: {
return prevState;
}
}
};
Problem is that prevState is already the object I want to set up in reducer.
I am checking (in another component) if filter has been changed and because of that I get prevProps and nextProps the same so no action will be triggered.
So somehow dispatched changes are already in reducer as prevProps.
In searchFilter reducer,you are returning the prevState as it is without mutating.
return prevState //which is reference to prevState = INITIAL_STATE.filter
correct way (no sure about state structure),
default: {
return {...prevState};
}
OR
return Object.assign({}, prevState);
#RIYAJKHAN was partially right.
Somehow I was modifying redux store directly, even if I made all object modifications via Object.assign(...).
In the end I had to create copy of object I was modifying - JSON.parse(JSON.stringify(filter)) and then update this new object and dispatch it to redux store
I understand basically what keys are for: we need to be able to identify changes in a list of children.
What I'm having trouble with is the information flow, and maintaining synchronicity between the states in my store and the states of subcomponents.
For example, say I have a list of <CustomTextInput> which is exactly what it sounds like. Some container with a text input inside of it. Many of them are stored in some <CustomTextInputList> element.
The number of CustomTextInputs in the list can change, they can be added and subtracted. I have a factory with an incrementing counter that issues new keys every time a CustomTextInput is inserted, no matter where it's placed.
I have a CustomTextInputModel type which populates my store. Whenever I change the value inside one of the inputs, it needs to call a callback which dispatches actions in my store. But then how do I know which one to modify, and how can I be sure that the whole list doesn't rerender from changing a single instance since the entire store's state is being recreated? Do I need to store a reference to some model ID in every CustomTextInput? Should this be the key?
Manythanks, I'm very new at this :)
But then how do I know which one to modify, and how can I be sure that the whole list doesn't re-render from changing a single instance since the entire store's state is being recreated?
If your component is tied to a list of objects in the redux store, then it will re-render the whole list since the parent would be getting new props. However there are a few ways to avoid the re-render.
Note that this is a pretty advanced post, but hopefully it helps. I find this page to be useful in explaining one way to make a react-redux application more performant in rendering large lists by having each item connected to the redux store and listening to itself via an id that is passed in via a prop.
Edit Heres another good article that illustrates the point: https://medium.com/devtravel/optimizing-react-redux-store-for-high-performance-updates-3ae6f7f1e4c1#.3ltrwmn3z
Inside of the Reducer:
function items(state = {}, action) {
switch (action.type) {
case 'MARK':
const item = state[action.id];
return {
...state,
[action.id]: {...item, marked: !item.marked}
};
default: return state;
}
}
function ids(state = [], action) {
return state;
}
In the list container:
<div>
{
ids.map(id => {
return <Item key={id} id={id} />;
})
}
</div>
// Returns an array of just ids, not whole objects
function mapStateToProps(state) {
return {id: state.ids};
}
Inside of the Item component:
// Uses id that is passed from parent, returns the item at the index equal to the id
function mapStateToProps(state, props) {
const { id } = props;
const { items } = state;
return {
item: items[id],
};
}
const markItem = (id) => ({type: 'MARK', id});
export default connect(
mapStateToProps,
{markItem}
)(Item);
Do I need to store a reference to some model ID in every CustomTextInput?
Yes, you will need some type of key/id to pass to the redux actions when you would like to make a modification to that specific item.
Should this be the key?
Most common way to reference something in the in the redux store would be by id.