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) }
})
}
Related
I am wanting to use Redux to perform multiple dispatches to the same endpoint to fetch different responses, however I am unsure of how I can best store them within the Redux store...
myAction.js
export const GET_DATA = 'GET_DATA';
export const GET_DATA_SUCCESS = 'GET_DATA_SUCCESS';
export const GET_DATA_ERROR = 'GET_DATA_ERROR';
export const getData = (path) => ({
type: GET_DATA,
agent: agent.getData.get,
agentData: path
});
myReducer.js
export default (state = {}, action) => {
...
switch (action.type) {
...
case GET_DATA_SUCCESS:
newState = action.payload;
break;
...
I will call the API like so:
const mapDispatchToProps = dispatch => {
getData: path => getData(path);
}
...
// Using dispatch
getData('/myDataPath');
agent.js
...
getData : {
get = path => performGet('https://my.url/data' + path);
}
Now I know this works perfectly fine for performing a single GET to the endpoint and storing in Redux store under a single entry.
Is there a way in which I can store multiple responses in the Redux store entry?
You could use a dynamic property using bracket notation in your state based on the agentData or other identifying piece of your action.
It would look something like this:
Reducer
export default (state = {}, action) => {
...
switch (action.type) {
...
case GET_DATA_SUCCESS:
newState = {
...state, // Keep all the other values (agentData entries)
[action.agentData]: action.payload // Add/overwrite the current one
};
break;
...
I have a useEffect() hook that I'd like to only update when the data in a state-full array changes. I know I can add that array as a dependency, but my issue is the array is not declared in the same file, it's coming from a redux store. Right now I removed the dependency and It works, but the dev tools show me that it is constantly sending new requests to the server when it should send a request only when submitted.
This is the function in the redux store:
/client/src/store/utils/thunkCreators.js
export const fetchConversations = () => async (dispatch) => {
try {
const { data } = await axios.get("/api/conversations");
dispatch(gotConversations(data));
} catch (error) {
console.error(error);
}
};
How do I import the conversations from the redux store as a dependency?
Home.js
useEffect(() => {
fetchConversations();
},[X]);
/client/src/store/utils/conversations.js
const GET_CONVERSATIONS = "GET_CONVERSATIONS";
export const gotConversations = (conversations) => {
return {
type: GET_CONVERSATIONS,
conversations,
};
};
const reducer = (state = [], action) => {
switch (action.type) {
case GET_CONVERSATIONS:
return action.conversations;
case SET_MESSAGE:
return addMessageToStore(state, action.payload);
case ADD_ONLINE_USER: {
return addOnlineUserToStore(state, action.id);
}
case REMOVE_OFFLINE_USER: {
return removeOfflineUserFromStore(state, action.id);
}
case SET_SEARCHED_USERS:
return addSearchedUsersToStore(state, action.users);
case CLEAR_SEARCHED_USERS:
return state.filter((convo) => convo.id);
case ADD_CONVERSATION:
return addNewConvoToStore(
state,
action.payload.recipientId,
action.payload.newMessage
);
default:
return state;
}
};
I am like in a strange problem. The problem is that I am trying to make an API hit (in service file) which in turn provides some data (it is working), this data is to be updated in my reducer1.js and then returned. Now, my issue is though the value is coming in reducer file, but is not returned, so in turn, state is not changed, and in turn my end component is not rerendered.
Now, when my service file is successfully hitting and then returning data to my reducer1.js, why in the world the updated-state is not returned by "GET_List" action type? Can someone see any problem?
index.js (service file)
const global = {
getActressList: async function(){
const response = await fetch("http://localhost:2000/api/actressList");
const data = await response.json();
return data;
}
}
export default global;
reducer1.js
import global from '../../services/index';
const initialState = {
data: [
{
id: 1,
name: "Aishwarya Rai",
src: "/assets/img/aishwarya.png"
}
]
};
function reducer1(state = initialState, action) {
switch (action.type) {
case "GET_LIST": {
const data = global.getActressList();
data.then((res)=> {
return {
...state,
data: res
}
})
}
default:
return state;
}
}
export default reducer1;
Result:
You are returning from a promise not from a reducer function:
function reducer1(state = initialState, action) {
switch (action.type) {
case "GET_LIST": {
const data = global.getActressList();
data.then((res) => {
// here you are returning from a promise not from a reducer function
return {
...state,
data: res,
};
});
}
default:
return state;
}
}
The code in reducer should be sync like this:
function reducer1(state = initialState, action) {
switch (action.type) {
case "GET_LIST": {
return {
...state,
data: action.payload,
};
}
default:
return state;
}
}
And your data fetching should be moved to component effect like this:
function YourComponent() {
const dispatch = useDispatch();
const data = useSelector(state => state.data)
useEffect(() => {
const data = global.getActressList();
data.then((res) => {
dispatch({type: 'GET_LIST', payload: res});
});
}, [])
...
}
EDIT
If you use class components the fetching logic should be placed in componentDidMount lifecycle hook like this:
class YourComponent extends Component {
state = { data: [] };
componentDidMount() {
const data = global.getActressList();
data.then((res) => {
dispatchYourAction({type: 'GET_LIST', payload: res});
});
}
...
}
I've run into an odd issue, where my redux store seems to be returning a duplicate of a different value? (Still learning terms so sorry if I mixed them up!)
I have 2 states. Users, and Added. I want to show to lists, one using the data from each one of them. currently, fetchUsers works fine, but fetchAdded shows Users for an unknown reason so both lists show the same data.
If I switch fetchUsers to use refAdded then it shows Added, so now it only shows the added array in both lists. I figured that means the actual calls are working cause it can get the data from Firebase, but I don't know why this would happen.
FetchUsers which gets a list of users from firebase looks like this:
export function fetchUsers() {
return (dispatch) => {
refUsers.on('value', snapshot => {
dispatch({
type: 'FETCH_USER',
payload: snapshot.val()
});
});
}
}
FetchAdded looks like this:
export function fetchAdded() {
return (dispatch) => {
refAdded.on('value', snapshot => {
dispatch({
type: 'FETCH_ADDED',
payload: snapshot.val()
});
});
}
}
The reducers look like this:
export default function(state = [], action) {
switch (action.type) {
case 'FETCH_USER':
return [action.payload];
case 'ADDED_USER':
return [action.payload, ...state];
case 'MOVE_USER':
const newState = [...state];
newState.splice(action.payload.index, 1);
return newState;
case 'MOVE_ITEM':
return [action.payload.user, ...state];
default:
return state
}
}
and fetch Added is:
export default function(state = [], action) {
switch (action.type) {
case 'FETCH_ADDED':
return [action.payload];
case 'MOVE_ITEM':
const newState = [...state];
newState.splice(action.payload.index, 1);
return newState;
case 'MOVE_USER':
return [action.payload.user, ...state]
default:
return state
}
}
I combine them both here:
const rootReducer = combineReducers({
users: UserReducer,
added: AddedReducer
});
and my firebase client exporting looks like this:
firebase.initializeApp(config);
export const refUsers = firebase.database().ref("users")
export const refAdded = firebase.database().ref("added")
export const auth = firebase.auth
export const provider = new firebase.auth.GoogleAuthProvider();
In my actual page where I display the 2 lists, this is what I have:
function mapStateToProps(state) {
return {
users: state.users,
added: state.added
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({ addUser, moveUser, moveItem, fetchUsers, fetchAdded }, dispatch);
}
I seem to have hit a snag when updating state using redux and react-redux. When I update an individual slice of state, all of the others get removed. I know the answer to this will be simple but I can't figure it out and haven't found anything else online.
So to clarify, here's my reducer:
const initialState = {
selectedLevel: null,
selectedVenue: null,
selectedUnitNumber: null,
selectedUnitName: null,
selectedYear: null
}
export default (state = initialState, action) => {
console.log('reducer: ', action);
switch (action.type){
case 'CHOOSE_LEVEL':
return action.payload;
case 'CHOOSE_VENUE':
return action.payload;
case 'CHOOSE_UNIT':
return action.payload;
case 'SHOW_COURSES':
return action.payload;
}
return state;
}
And my combine reducer:
export default combineReducers({
workshopSelection: WorkshopSelectReducer
});
So my initial state looks like this:
workshopSelection: {
selectedLevel: null,
selectedVenue: null,
selectedUnitNumber: null,
selectedUnitName: null,
selectedYear: null
}
But when I use one of my action creators, for example:
export function chooseVenue(venue){
return {
type: 'CHOOSE_VENUE',
payload: {
selectedVenue: venue
}
}
}
I end up with state looking like this:
workshopSelection: {
selectedVenue: 'London',
}
All of the rest of the state within this object that wasn't affected by this action creator has been completely wiped out. Instead, I just want all other entries to stay as they are with their original values - null in this example, or whatever other value has been assigned to them.
Hope that all makes sense.
Cheers!
You are basically replacing one object (previous state) with another one (your payload, which is also an object).
In terms of standard JS, this would be the equlivalent of what your reducer does:
var action = {
type: 'CHOOSE_VENUE',
payload: {
selectedVenue: venue
}
};
var state = action.payload;
The simplest way to fix this would be using Object spread properties:
export default (state = initialState, action) => {
switch (action.type){
case 'CHOOSE_LEVEL':
case 'CHOOSE_VENUE':
case 'CHOOSE_UNIT':
case 'SHOW_COURSES':
// Watch out, fall-through used here
return {
...state,
...action.payload
};
}
return state;
}
... but since this is still in experimental phase, you have to use some other way to clone previous properties and then override the new ones. A double for ... in loop could be a simple one:
export default (state = initialState, action) => {
switch (action.type){
case 'CHOOSE_LEVEL':
case 'CHOOSE_VENUE':
case 'CHOOSE_UNIT':
case 'SHOW_COURSES':
// Watch out, fall-through used here
const newState = {};
// Note: No key-checks in this example
for (let key in state) {
newState[key] = state[key];
}
for (let key in action.payload) {
newState[key] = action.payload[key];
}
return newState;
}
return state;
}
Keep your payload object as flat on actions creators as shown below...
export function chooseVenue(venue){
return {
type: 'CHOOSE_VENUE',
selectedVenue: venue
}
}
and modify your reducer as below (given example is for updating the venue, do the same for other cases too...)
export default (state = initialState, action) => {
let newState = Object.assign({}, state); // Take copy of the old state
switch (action.type){
case 'CHOOSE_LEVEL':
case 'CHOOSE_VENUE':
newState.selectedVenue = action.selectedVenue; // mutate the newState with payload
break;
case 'CHOOSE_UNIT':
case 'SHOW_COURSES':
default :
return newState;
}
return newState; // Returns the newState;
}