I'm trying to add an array of layers using redux.
First, I create an array of promises.
Secnod, I use Promise.all with promises array and send to database all the info and returns all layers created on database.
Third, totalLayers contains current layers with new layers from database.
My problem is that launch dispatch and draw layers on my map, but dont update the array of redux with totalLayers.
SET_MAP_LAYERS update layers stored in Store as you can see in mapGlLayers variable.
What I'm doing wrong??
static addMultipleLayersFromDataSet(layers, source) {
return (dispatch) => {
let mapGlLayers = store.getStore().getState().maplayers.maplayers.slice();
let position = mapGlLayers.filter(l => l.name).length;
let promises = layers.map( layer => uploadMultipleLayers(layer, source, position++));
Promise.all(promises)
.then(downloadedlayers => {
let totalLayers = [...mapGlLayers, ...downloadedlayers];
dispatch({
type: LayerTypeConstants.SET_MAP_LAYERS,
payload: totalLayers
});
})
.catch(error => {
dispatch({
type: LayerTypeConstants.MAPLAYER_ERROR,
payload: error
});
});
};
}
REDUCER:
import { LayerTypeConstants } from '../utils/ReduxConstants';
const initialStateApp = {
maplayers: [],
};
export default function LayerReducer(state = initialStateApp, action) {
switch (action.type) {
case LayerTypeConstants.SET_MAP_LAYERS: {
return Object.assign({}, state, {
maplayers: action.payload
});
}
case LayerTypeConstants.MAPLAYER_ERROR: {
return Object.assign({}, state, {
messageMapLayer: action.payload
});
}
case LayerTypeConstants.INIT_LAYERS:
return Object.assign({}, initialStateApp);
default:
return state;
}
};
Here is an image of my redux state:
Here is console message with layers:
Promise is a state machine under the hood. State machines perform state transitions. Redux alone is not prepared to handle such transitions so it heeds a helper. I'd suggest reading about Async Redux Actions, link. The two widely used helpers are redux-thunks and redux-saga.
Related
In a previous scenario to Update Profile values, i created a new store slice (updatedProfileDetails) and stored a new object there, but i think this isn't the best practice to solve my problem (since i have now two slices profileDetails & updateProfileDetails),
Now I found in redux documentation that we can make immutable update reducers to change data in the same slice without mutate states but in this scenario (set conversation unseen to false) , the API don't send an object as a response but just a success message,
SO, I'm trying to passe my conversation ID from Action To reducer to check it and change a single value of this conversation[id]
but i'm unable to make it correctly
My code:
1- Action: where I'm supposed to send id after action success
export const updateSeenConversation = (id) => async (dispatch, getState) => {
try {
dispatch({
type: SEEN_CONVERSATIONS_REQUEST,
})
const {
userLogin: { userInfo },
} = getState()
const config = {
headers: {
// headers
},
}
const { data } = await axios.put(
`${process.env.REACT_APP_API_KEY}/conversation/unseen/${id}`,
"",
config
)
// if success data : {success:true , message:"..."}
dispatch({
type: SEEN_CONVERSATIONS_SUCCESS,
payload: id, // usually i send data sent from API as dispatch payload,, to check for the exact conversation to update i tried to send id
})
} catch (error) { //error...})
}
}
2- my reducer:
export const conversationsListReducer = (
state = { conversations: {} },
action
) => {
// get conversations works Fine and return a slice conversations{..} that i need to update
switch (action.type) {
case GET_CONVERSATIONS_REQUEST:
return { ...state, loading: true }
case GET_CONVERSATIONS_SUCCESS:
return { loading: false, conversations: action.payload }
case GET_CONVERSATIONS_FAIL:
return { loading: false, error: action.payload }
// here ERROR
// i cant access the exact value i want "unseen" **NB:CHECK store in IMAGE BELLOW**
case SEEN_CONVERSATIONS_REQUEST:
return { ...state, loading: true }
case SEEN_CONVERSATIONS_SUCCESS:
return {state.conversations.conversation.map((conversation) => {
if (conversation._id !== action.payload) {
return conversation // return without change if !== myid
}
return {
...conversation, // make a copy
unseen: false, // change this value to false/true
}
})}
case SEEN_CONVERSATIONS_FAIL:
return { loading: false, error: action.payload }
case USER_LOGOUT:
return { conversations: {} }
default:
return state
}
}
Redux store Slice Image Link
Thank You.
Ok, I still don't see where in your post you say what isn't working but I'm gathering it is something in the SEEN_CONVERSATIONS_SUCCESS reducer case since that's where you focused some comments.
From what I can tell in this reducer case, you are mutating the state invariant from { conversations: {} } to { [] } since mapping returns an array, and isn't a valid object. When updating state you need to shallow copy the state object and also any nested state being updated.
case SEEN_CONVERSATIONS_SUCCESS:
return {
...state, // <-- shallow copy state
conversations: {
...state.conversations // <-- shallow copy state.conversations
conversation: state.conversations.conversation.map( // <-- shallow copy conversation array
conversation => conversation._id === action.payload
? {
...conversation, // <-- shallow copy conversation
unseen: false
}
: conversation
),
}
};
Since this reducer case is accessing nested state properties that are possibly undefined, you should also fully declare your state.
const initialState = {
conversations: {
conversation: [], // <--- now access this deep won't throw error
},
loading: false,
error: null,
};
export const conversationsListReducer = (
state = initialState,
action
) => {
...
case USER_LOGOUT:
return initialState; // <-- reset to initial state
When working with Redux, maintaining the shape of the initial state is crucial. The results/data of side effects like API call will change the shape of the state since we have no control over the properties. For example, consider this initial state:
const book = {
id: 0,
name: 'something'
};
And updation is made to it by the book sub-reducer as follows based on the API data:
//receives `book` part of the state
const bookReducer = (state=book, action) => {
switch(action.type) {
case 'SET_BOOK': {
return { ...action.payload };
} default:
return state;
}
}
Two scenarios that could happen:
If the data sent from the API is null, then newly produced state is now {} as a result of the spread operator. If some parts of UI were to listen to the book part of the state, then it will break. Possibly access individual properties from the API data? In that case, null/undefined checks needs to be performed for properties. Is there a more elegant solution?
There could also be additional properties in the data which we may not be interested in. Possibly use an object mapper to filter unused properties?
What is the best practice to handle these kind of scenarios and prevent state becoming non-deterministic? Please share your experience on how you approached these scenarios.
Only the reducer has to be pure/deterministic, not the stuff outside of it.
To prevent your reducer from overwriting data incorrectly, write some logic between the API response and the dispatch-call to ensure the reducer always gets valid data.
For example a thunk might look like:
const createBook = (name) => {
return async dispatch => {
// suppose the api call gives back "uid" plus extra data
const { uid, ...unneededData } = await myApi.setBook(name);
// dispatch the data in the way the reducer expects it
dispatch({ type: 'SET_BOOK', id: uid, name });
}
}
In the above example, the api call gives me uid, but no name, and a bunch of extra data. Just prepare the data before dispatching it.
The best practice is the one where you prevent your app from breaking from every aspect, which means you need to check and format your data before returning from the reducer.
In your case, I would check for both data validity and map it to a necessary format.
only dispatch 'SET_BOOK' if API response has both id and book.
in order to avoid unnecessary additional properties, you can always map your data const book = {id: apiData.id, book: apiData.book} before dispatching.
In your reducer you can do like below. This way only id and name will get updated even though there are other key/values in the response. Also this will make sure that if null values are received then those values will not be updated in the state. Hope this will help in resolving the issue.
//receives `book` part of the state
const bookReducer = (state=book, action) => {
const { type, payload } = action;
switch(type) {
case 'SET_BOOK': {
return {
...state,
...(payload.id && {id: payload.id}),
...(payload.name && {name: payload.name})
};
} default:
return state;
}
}
Your redux reducer logic should not worry about that due to its deterministic nature. You handle your api call and response handling elsewhere (redux thunk or component), and then dispatch the action to set your redux. Building off of your example:
book.reducer.js
const book = {
id: 0,
name: ''
};
const bookReducer = (state=book, action) => {
switch(action.type) {
case 'SET_BOOK': {
return { ...action.payload };
} default:
return state;
}
book.actions.js
const setBook = (book) => ({
type: SET_HEROES,
payload: book
});
// thunk
const findBook = name => async dispatch => {
const book = await bookService.findBook(name);
if (book) {
dispatch(setBook(book));
}
};
book.service.js
const findBook = async (name) => {
// Do your api call
const bookResponse = axios.get(`${apiUrl}/book/search/${name}`);
// Handle the response
if (!bookResponse) {
// Logic you do if book not found
return null;
}
return {id: bookResponse.id, name: bookResponse.name};
}
Now in a component you can just dispatch the findBook call
Component.js
const Component = () => {
const [search, setSearch] = useState('');
const dispatch = useDispatch();
const handleOnSearch = () => {
dispatch(findBook(search));
}
return (
<div>
<input value={search} onChange={(e) => setSearch(e.target.value)}/>
<button onClick={handleOnSearch}>Search</button>
</div>
);
}
If field value from API is undefined then convert it into null and store so that the code doesn't break and operatable. If API gives other params as well then de-structure the API returned object and extract the required fields. So that storing unnecessary data can be avoided.
const bookReducer = (state=book, action) => {
switch(action.type) {
case 'SET_BOOK': {
const {id, name, otherParam1, otherParam2} = action.payload;
return {
id: id || null,
name: name || null,
otherParam1,
otherParam2
}
} default:
return state;
}
}
Having the value null won't break the code instead, it renders nothing
which is better than undefined which breaks the code
Hope this helps you
What I do is to have all of my logic in my action method and create reducers for when an action is correctly fulfilled and another one for when is rejected. In the fulfilled reducer, I would do the regular instructions and in the rejected reducer I would add the data to a variable called error which I always have in my state and use in the frontend to show an error message if needed.
Example
This is an action that creates a house by sending a post request to my api which returns the created object or an error if something went wrong.
export const createHouse = houseData => {
const URL = HTTP://EXAMPLE.URL
return async dispatch => {
try {
const response = await axios.post(`${URL}`, houseData);
const data = await response.data;
dispatch({ type: "CREATE_HOUSE_DRAFT_FULFILLED", data });
} catch (err) {
dispatch({ type: "CREATE_HOUSE_DRAFT_REJECTED", data: err });
}
};
};
Then I would have 2 reducer methos to recieve the fulfilled or the rejected response, like this.
case 'CREATE_HOUSE_DRAFT_FULFILLED': {
return {
houses: [action.data, ...state.houses],
house: action.data,
houseCount: state.houseCount + 1,
fetched: true,
error: null
};
}
case 'CREATE_HOUSE_DRAFT_REJECTED': {
return {
...state,
error: action.data.response.data,
fetched: false,
success: null
};
}
Hope this works for you!
I am using redux for an api-centric application and having trouble with the concept of master and detail retrievals.
Say I get may master list with a redux-thunk action like this:
// action:
export const fetchItems = params => async (dispatch, getState) => {
dispatch({type: FETCH_ITEMS_REQUEST});
let response = await api.fetchItems(params);
dispatch({type: FETCH_ITEMS_SUCCESS, payload: normalize(response, schema)});
}
Then I want to drill down on a single item that I retrieve like this:
export const fetchItem = id = async (dispatch, getState) => {
dispatch({type: FETCH_SINGLE_ITEM_REQUEST});
let response = await api.fetchItem(id);
dispatch({type: FETCH_SINGLE_ITEM_SUCCESS, payload: normalize(response, schema)});
}
They both end up in my reducer in the same way -
const reducer = (state, {type, payload}) => {
switch(type) {
case FETCH_ITEMS_SUCCESS:
case FETCH_SINGLE_ITEM_SUCCESS:
return {
...state,
allIds: payload.result.data,
byId: payload.entities.item,
error: null,
fetching: false
};
case FETCH_ITEMS_REQUEST:
case FETCH_SINGLE_ITEM_REQUEST:
// how do I/should I separate fetching id=1 vs fetching all items??
return { ...state, fetching: true, error: null };
case FETCH_ITEMS_FAILURE:
case FETCH_SINGLE_ITEM_FAILURE:
// how do I/should I separate fetching id=1 vs fetching all items for error??
return { ...state, fetching: false, error: payload };
}
}
I don't think I should be sharing the fetching and error as variables for the single item and all items.
I started down the road of hashing errors and fetching status by id for the single item, but it's kind of ugly.
Should the single item be moved into its own reducer? In which case should it not follow the allIds/byId pattern? I don't think I want to do that because I want to introduce caching so I can check if the record exists before querying the API.
How do I structure my redux reducers for both many and the single record?
I am creating an async action in Redux with Redux-Thunk and while the action works and returns the data, when it is added to the state it is put inside an object with the same name as the reducer, like so...
The posts key should just have the array of items but it is instead an object with a posts key. What could be causing this issue? Here is the code for the async action...
export function getApiData() {
return dispatch => {
fetch('https://jsonplaceholder.typicode.com/posts')
.then(response => response.json())
.then(json => dispatch(
{
type: ActionTypes.GET_API_DATA,
payload: json
}
))
}
}
Here is the code for the reducer...
function posts(state = [], action) {
switch (action.type) {
case ActionTypes.GET_API_DATA:
return {
...state,
posts: action.payload,
}
default:
return state
}
}
The other keys like user and prizes (which are not async actions) work perfectly fine and the new data is added as expected to them. What is leading to this behavior when it comes to the posts key and the async action?
Thanks in advance.
Thge error is in how you process the action in posts reducer. You need to return the array instead of an object
function posts(state = [], action) {
switch (action.type) {
case ActionTypes.GET_API_DATA:
return [
...state,
...action.payload,
] // or return action.payload if you don't want to merge data
default:
return state
}
}
Are you sure that the response is in the correct format? Could you try attach a debugger or a console.log to see the data? Or try send to dispatch the json.data.
I am trying to understand Redux and having some difficulty.
I understand the concept of combineReducer, ie ....
var reducer = combineReducers({
user: userReducer,
products: productsReducer
})
But what if I have thousands of products, only available on the products page. I do not understand why I need to load them at root; to me this will slow the initial start up of the app for something that will not be needed unless the user goes to the products page.
Is this just the way it is with redux?
In Redux apps, you always build your entire state at the start. With Redux you have one store and one state - everything should trickle down from that one state to props on your components. However, that does not mean you actually need to load all the data into the state at launch, only that the structure needs to be there. This is why you should set up an initial state object for each reducer.
Let's say you have thousands of product records that you load from the database. In your products reducer you could do something like this:
const initialState = {
data: []
};
//use ES6 default parameters
function productsReducer (state = initialState, action) {
switch (action.type) {
case 'GET_PRODUCTS':
//return data from action
return {
data: action.result
};
default:
return state;
}
}
This means that when you start your app, if you use the full reducer you declared in your post, your application state will look like this:
{
user: {},
products: {
data: []
}
}
products.data will be an empty array until you fire an action that actually requires you to load the products data (i.e. you go to the Products page in your app or something). It's true that the products data will remain in your state if you then go elsewhere in your app, but this is a great thing - the next time you render the Products page you will already have the data at your disposal without having to do a database lookup.
In our app, we made an API for the products and it has limit of 15 per page. So our reducer goes like this.
collection: {
"total": 0,
"per_page": 0,
"current_page": 0,
"last_page": 0,
"from": 0,
"to": 0,
data: []
},
isFetching: false,
isFetchingError: false
on the first load we fetched limited amount of products, then we made a pagination out of it.. using selectors in redux https://github.com/rackt/reselect
Loading a thousands of data will get your app very slow.
const paginated = (state = initialState, action) => {
switch (action.type) {
case FETCH_PAGINATED_PRODUCTS:
return {
...state,
isFetching: true,
isFetchingError: false
};
case FETCH_PAGINATED_PRODUCTS_SUCCESS:
return {
...state,
collection: action.payload,
isFetching: false
};
case FETCH_PAGINATED_PRODUCTS_ERROR:
return {
...state,
isFetching: false,
isFetchingError: true
};
default:
return state
we have used axios for request:
https://github.com/mzabriskie/axios
Here's how we implement axios in redux-async
export function getAll(page = 1) {
return (dispatch, getState) => {
const state = getState();
const { filters } = state.products.paginated;
if ( state.products.paginated.isFetching ) {
return;
}
dispatch({ type: FETCH_PAGINATED_PRODUCTS });
return axios
.get(`products?page=${page}&limit=16&filters=${JSON.stringify(filters)}`)
.then((res) => dispatch({
type: FETCH_PAGINATED_PRODUCTS_SUCCESS,
payload: res.data
}))
.catch((res) => dispatch({
type: FETCH_PAGINATED_PRODUCTS_ERROR,
/*payload: res.data.error,*/
error: true
}));
}
}
export function get(id) {
return (dispatch, getState) => {
const state = getState();
if ( state.products.resource.isFetching ) {
return;
}
dispatch({ type: FETCH_PRODUCT });
return axios
.get(`products/${id}`)
.then((res) => dispatch({
type: FETCH_PRODUCT_SUCCESS,
payload: res.data.data
}))
.catch((res) => dispatch({
type: FETCH_PRODUCT_ERROR,
/*payload: new Error(res.data.error),*/
error: true
}));
}