I have a page, which has two sections:
1) List of questions;
2) Top-voted list of questions
These two queries use the same call to the backend API, the only difference is I pass an additional parameter, e.g. showPopular=true in the latter case.
When I try to render data, whatever action retrieves last overwrites data in both sections. How do I distinguish such data?
I am using ReactJS, Redux.
This is to retrieve data:
query={showPopular:true}
this.props.actions.loadQuestions(accountId, query);
I have
const mapStateToProps = (state) => {
return {
error: state.questions.error,
questions: state.questions.questions|| [],
isLoading: state.questions.isLoading
};
}
You should at least separate state/prop/reducer because this two list are totally different, and they obviously will render different DOM.
The mapState should have different props mapping:
const mapStateToProps = (state) => {
return {
error: state.questions.error,
questions: state.questions.questions|| [],
topQuestions: state.questions.topQuestions,
isLoading: state.questions.isLoading
};
}
You didn't post the reducer but it should also separate the two action or by using condition to update different state:
export default (state = initialState, action) => {
switch (action.type) {
case LOAD_QUESTION:
if(action.query.showPopular){
return state.set('topQuestions', action.data);
}else{
return state.set('questions', action.data);
}
default:
return state;
}
};
Related
Im currently using redux to manage my state. The scenario is as such , Upon successful creation of a new object , i would like to append the response data into my existing state container as i don't wish to make a new API call to render it.
initial State:
const initialState = {
workflowobject:{},
};
SAGA:
export function* workerCreateTransitionApproval(action) {
const data = yield call(() => axiosInstance.post(`/transition-approval-meta/create/`, action.data))
yield put({ type: "STORE_WORKFLOW_DATA", payload: data.data.data, fetch: 'workflowobject' , label: 'transition_approvals'})
}
So over here , i upon recieving the "signal" so to speak to create a transition approval , i will catch that event and create make an axios post request to my backend , which will then return a response of the transition_approval . I will then store this transition_approval as the payload which i will use later on.
Reducer
const loadWorkflowObject = (state, action) => {
return updateObject(state, {
workflowobject: { ...state.workflowobject, [action.label]: action.payload }
})
}
const storeData = (state, action) => {
switch (action.fetch) {
case 'workflowobject': return loadWorkflowObject(state, action)
}
}
const reducer = (state = initialState, action) => {
switch (action.type) {
case 'STORE_WORKFLOW_DATA': return storeData(state, action);
case 'CLEAR_CLASS_STATES': return clearClassStates(state, action);
case 'CLEAR_OBJECT_STATES': return clearObjectStates(state, action);
default:
return state;
}
}
export default reducer;
So in my reducer , it will first go into the case STORE_WORKFLOW_DATA which will then return the reducer function loadWorkflowObject . This is where i wish to 'append' the data back to the state tree.
The problem
The tricky part here is that im using this loadWorkflowObject reducer for fetching data too , and im already using the spread operator here.
The code that i have shown above will override my preexisting data that i have in the transition_approvals object , if possible , i would like to append the data in instead.
you can do this:
const loadWorkflowObject = (state, action) => {
return updateObject(state, {
workflowobject: { ...state.workflowobject, [action.label]: state. transition_approvals.concat(action.payload) }
})
}
I am creating a useReducer that, e.g., sets the price of an item.
const reducer = (state, action) => {
switch (action.type) {
case 'SET_PRICE':
return {
...state,
price: action.payload,
}
default:
return state
}
}
I now have to have multiple of these SET_PRICE, as I have hundreds of products, so I started writing
case 'SET_PRICE_0':
return {
...state,
price_0: action.payload,
}
case 'SET_PRICE_1':
return {
...state,
price_1: action.payload,
}
...
I can see that there's something wrong if I write over 10 of these, let alone 100... Is there an alternative to write less code?
You may wanna refactor your code. This approach, case 'SET_PRICE_1, will not scale. Can you post an example of Api response?
Depending on your data structure, you should have something like:
products: [
{
productName: 'productName',
price: '$$$',
},
]
or maybe
products: {
[productName]: {
price: '$$$',
}
}
in case, you just need the product price (this will not scale too),
productPrices: {
[productName]: '$$$'
}
I believe the issue is not with the reducer you have mentioned, but the way you're using it. While rendering the products, you should use useReducer hook with the first reducer (the one with SET_PRICE) while mounting each product component.
Here's a dummy implementation:
const ProductComponent = () => {
const [price, setPrice] = useReducer(reducer, initialState);
return (
<div> ... </div>
);
};
And then you can map on your products list with this component.
You can also refer to this detailed article on different useReducer recipes: https://medium.com/crowdbotics/how-to-use-usereducer-in-react-hooks-for-performance-optimization-ecafca9e7bf5
Link to the repo: https://github.com/charles7771/ugly-code
At the 'Options' component, I am not writing anything dynamically for a form I'm creating, I am giving each one of the reducer cases a different name. That is not scalable at all and I have no idea how to go about fixing it. Any thoughts?
It is something like this:
case 'SET_SMALLPRICE0': //goes from 0 to over 20
return {
...state,
smallPrice0: action.payload,
}
case 'SET_MEDIUMPRICE0':
return {
...state,
mediumPrice0: action.payload,
}
You could name the action type the exact name of the property to update, or perhaps with a "set_" in front. That would allow you to automatically set that property.
const reducerPriceCarSizeEachService = (state, action) => {
return { ...state, [action.type]: action.payload }
}
or, with a "set_" in front (this could allow you to use different prefixes, such as "push_", "delete_" depending on what you actually want to do)
const reducerPriceCarSizeEachService = (state, action) => {
if (action.type.startsWith('set_')) {
let prop = action.substr(4)
return { ...state, [props]: action.payload }
}
return state
}
Cannot work out what is going on here but basically i have a json file which has a load of products. im trying to then render the ones I want
here is my reducer:
export default(state = initialState, action) => {
switch(action.type){
case Types.SHOW_PRODUCTS: {
console.log('here1');
let productsToShow = data.filter(category => category.link === action.category)
const newState = [].concat(productsToShow[0].products)
return newState;
}
default:
console.log('here2');
return state;
}
}
when I log the state in my store, it says that productsToRender is an array of length 5 (this is correct)
however, when I log (this.props) in one of my components, it says that the length is 0
in the above reducer, the console.log('here 1') is the last console log being called, meaning that it is definitely returning products ( that is verified in the store state). so im not sure why it is then wiping it in that component?
in that component I call this
componentWillMount = () => {
this.props.showProducts(this.props.chosenCategory.category)
}
which passes in the chosen category so I now what products to render
however, logging this.props in the render method below, is showing it to be an empty array
of course I can post more code if necessary but any reason for this funky behaviour?
extra info:
interestingly when I do this:
default:
console.log('here2');
return [{name: 'prod'}];
}
and then log this.props, it now contains this array with this object???
The store should be immutable, that is, the value you return should be made immutable.
I assume you are adding only a single array in the store
Try changing the reducer like,
const initialState = [];
export default(state = initialState, action) => {
switch(action.type){
case Types.SHOW_PRODUCTS: {
console.log('here1');
let productsToShow = data.filter(category => category.link === action.category)
let newState = [...state,...productsToShow[0].products]
return newState;
}
default:
console.log('here2');
return state;
}
}
I have a redux application where i initially async fetch some messages from my server.
I've hooked up the app to a signalR connection, and when the user submits a message, i need to to be added to the Redux state.
However it seems like i cant update the state, or at least im doing it wrong.
When the server send a new message to the client i call my action addMessagesSignalR
store.dispatch(addMessagesSignalR(messageFromServer));
The action is
export function addMessagesSignalR(Obj) {
let messageFromServer = Obj;
return {
type: 'FETCH_MESSAGES_SIGNALR',
payload: messageFromServer
};
}
And my reducer is:
export default function(state = [], action) {
switch(action.type) {
case 'FETCH_MESSAGES':
return Object.assign({}, state, action.payload);
case 'FETCH_MESSAGES_SIGNALR':
return Object.assign({}, state, action.payload)
}
return state;
}
Im trying to merge the state of the fetched messages with the message recieved from signalR in my rootReducer:
messages: fetchMessages
Object.assign is used to merge objects, but your state appears to be an array. You need to use different syntax to merge arrays.
ES6 (using spread syntax):
export default function(state = [], action) {
switch(action.type) {
case 'FETCH_MESSAGES':
return [...state, action.payload];
case 'FETCH_MESSAGES_SIGNALR':
return [...state, action.payload]
}
return state;
ES5 (using Array.concat):
export default function(state = [], action) {
switch(action.type) {
case 'FETCH_MESSAGES':
return state.concat(action.payload);
case 'FETCH_MESSAGES_SIGNALR':
return state.concat(action.payload)
}
return state;