I am using easy-peasy as the state manager of react application
In the actionOn I need to access state of another model, How can I access todos.items in the notes.onAddNote ?
import { createStore, StoreProvider, action, actionOn } from 'easy-peasy';
const todos= {
items: [],
addTodo: action((state, text) => {
state.items.push(text)
})
};
const notes = {
items: [],
addNote: action((state, text) => {
state.items.push(text)
}),
onAddNote: actionOn(
(actions, storeActions) => storeActions.notes.addNote,
(state, target) => {
// HOW TO READ todos items
}
),
};
const models = {
todos,
notes
};
const store = createStore(models);
making onAddNote a thunkOn instead of actionOn
Related
So I'm creating something like "Trello" clone with react redux nodejs and mongoDB and i have some issue.
The problem is when I add a card to a list its not update the redux state, so I will see the card in the list only after a refresh page. (the card added to the DB but not to redux state).
just for more info: boardlists is an array inside the object board from mongo, inside that array there is objects of list, inside each of them there is an array of cards.
here is my code:
REDUCER
const initialState = {
boardLists: [
],
};
export default function (state = initialState, action) {
switch (action.type) {
case FETCH_ITEMS_BEGIN:
return {
...state,
loading: true,
errors: null,
};
case FETCH_ITEMS_SUCCESS:
return {
...state,
loading: false,
boardLists: action.payload.data.boardLists,
};
case FETCH_ITEMS_FAILURE:
return {
...state,
loading: false,
errors: action.payload.errors,
boardLists: [],
};
//handless creation of data
case ADD_LIST:
return {
boardLists: [...state.boardLists, action.payload.list],
};
case ADD_CARD:
return {
boardlists: [...state.boardlists, action.payload.card],
}
ACTIONS
export const fetchItemsBegin = () => ({
type: FETCH_ITEMS_BEGIN,
});
export const fetchItemsSuccess = (data) => ({
type: FETCH_ITEMS_SUCCESS,
payload: { data },
});
export const fetchItemsFailure = (errors) => ({
type: FETCH_ITEMS_FAILURE,
payload: { errors },
});
//dispatched when item needs to be created
export const addList = (list) => {
return {
type: ADD_LIST,
payload: { list },
};
};
// add card
export const addCard = (card) => {
return {
type: ADD_CARD,
payload: { card }
};
};
//dispatched when all the lists from board stored in redux store needs to be read
export const getBoardLists = () => {
return (dispatch) => {
// function starts
dispatch(fetchItemsBegin()); // fetching begins
return http
.get(`${myUrl}/boards/one`) // req data from server
.then(({ data }) => {
console.log(data);
// if data is found
dispatch(fetchItemsSuccess(data)); // success
})
.catch((error) => dispatch(fetchItemsFailure(error))); //errors
};
};
COMPONENT THAT HANDLE THE ADD FUNCTION
handleAddCard = () => {
//add card
const { text } = this.state;
const { listID } = this.props;
const newCard = {
// _id: uuidv4(),
text,
};
cardService.createCard(newCard, listID);
this.props.addCard(newCard);
};
.
.
.
.
.
const mapStateToProps = ({ boardLists, loading, errors }) => ({
boardLists,
loading,
errors,
});
export default connect(mapStateToProps, { addList, addCard, getBoardLists })(ActionButton);
It appears you need to update an object in your lists array, and not add the card item to the list array itself.
In the actions:
// add card
export const addCard = (card, listId) => {
return {
type: ADD_CARD,
payload: { listId, card }
};
};
In the Reducer, you will need to find the list with matching id and add the card to its array e.g.:
case ADD_CARD:
const {listId, card} = action.payload;
return {
...state,
boardLists: state.boardLists.map(list => {
list.cards = list.cards || [];
return list.id === listId ? {...list, cards: [...list.cards, card]} : list
}),
}
This other question on stack overflow could be useful for this part. link
I am working on a reactjs ecommerce app for that I need to check in productDetail Page is the product is already available in cart page. Both of the components have different api endpoint. I am not sure how to do that. I am using react-redux for state management.
CartActions.js
export const getItemsAddedToCart = () => dispatch => {
dispatch({
type: cartActionTypes.GET_CART_LOAD
});
new _rest()
.get(URLConstants.urls.GET_ALL_ITEMS_IN_CART)
.then(response => {
dispatch({
type: cartActionTypes.GET_CART_SUCCESS,
payload: response.data
});
})
.catch(err => {
dispatch({
type: cartActionTypes.GET_CART_ERROR,
error: err
});
});
};
export const IsItemInCart = (pId) => ({
type: cartActionTypes.CHECK_ITEM_IN_CART,
pId
});
These are my actions inside productDetails page i am passing product id inside IsItemIncart. Then In reducer I am trying to look into cart ans update state.
CartReducer.js
const initialState = {
loading: false,
loaded: false,
error: null,
itemsInCart: null,
pagination: null,
totalamount: 0,
checkedItems: [],
paymentStatus: "NIL_TRANSACTION",
order: {
addressId: "NIL",
paymentType: "NIL"
},
isItemInCart: false,
bulkOrderData: []
};
export default function (state = initialState, action) {
switch (action.type) {
case cartActionTypes.GET_CART_LOAD:
return {
...state,
loading: false
};
case cartActionTypes.GET_CART_SUCCESS:
return {
...state,
loaded: true,
loading: false,
totalItemInCart: action.payload._embedded.cartResourceList.length,
itemsInCart: action.payload._embedded.cartResourceList,
totalamount: action.payload._embedded.cartResourceList.reduce(
(acc, item) => {
return item.totalAmount + acc;
},
0
),
pagination: action.payload.page
};
case cartActionTypes.CHECK_ITEM_IN_CART:
const findItem = state.itemsInCart.findIndex(
productId => productId === action.pid
);
return {
...state,
isItemInCart: true,
/*
* Check For Item In Cart Here
* */
};
Here is My reducer I want to check them here which i am not sure how to do that.
In CartAction.js
export const IsItemInCart = (productId) => {
return (dispatch, getState) => {
// you can use your cart reducer name instead of cartData
const cartData = getState().cartData;
// match your productId with your products in cart
const product = cartData.find((singleProductInCart) => {return singleProductInCart.productId === productId});
// then proceed further depending on you get product or not
}
}
Actions in redux are meant to transform the state as you've done with getItemsAddedToCart. IsItemInCart is not how you should use actions as checking whether a particular item is in the cart is a derived property that can be calculated outside the store (in the component or otherwise) like below:
import React from 'react';
import { connect } from 'react-redux';
import { getItemsAddedToCart } from './CartActions';
// these will get mapped to the components props
// using the connect function from react-redux
const mapDispatchToProps = (dispatch) => ({
fetchCartItems: () => dispatch(getItemsAddedToCart())
})
const mapStateToProps = (state, props) => ({
cartList: state.cart.itemsInCart,
});
class ExampleComponent extends React.Component {
constructor(props) {
super(props);
// if has not been fetched yet
if (!this.props.cartList) {
this.props.fetchCartItems();
}
}
checkItemInCart(pID) {
return this.props.cartList.find(
(product) => product.id === pID);
}
....
....
}
export default connect(mapStateToProps, mapDispatchToProps)(ExampleComponent);
Read usage with react.
I am migrating my component from a class component to a functional component using hooks. I need to access the states with useSelector by triggering an action when the state mounts. Below is what I have thus far. What am I doing wrong? Also when I log users to the console I get the whole initial state ie { isUpdated: false, users: {}}; instead of just users
reducers.js
const initialState = {
isUpdated: false,
users: {},
};
const generateUsersObject = array => array.reduce((obj, item) => {
const { id } = item;
obj[id] = item;
return obj;
}, {});
export default (state = { ...initialState }, action) => {
switch (action.type) {
case UPDATE_USERS_LIST: {
return {
...state,
users: generateUsersObject(dataSource),
};
}
//...
default:
return state;
}
};
action.js
export const updateUsersList = () => ({
type: UPDATE_USERS_LIST,
});
the component hooks I am using
const users = useSelector(state => state.users);
const isUpdated = useSelector(state => state.isUpdated);
const dispatch = useDispatch();
useEffect(() => {
const { updateUsersList } = actions;
dispatch(updateUsersList());
}, []);
first, it will be easier to help if the index/store etc will be copied as well. (did u used thunk?)
second, your action miss "dispatch" magic word -
export const updateUsersList = () =>
return (dispatch, getState) => dispatch({
type: UPDATE_USERS_LIST
});
it is highly suggested to wrap this code with { try } syntax and be able to catch an error if happened
third, and it might help with the console.log(users) error -
there is no need in { ... } at the reducer,
state = intialState
should be enough. this line it is just for the first run of the store.
and I don't understand where { dataSource } comes from.
Component is not updating even though vuex store is updated, tried by using to computed.
store.js
const actions = {
addToInProgress: ({ commit, state }, incidentId) => {
commit(Type.ADD_TO_PROGRESS, incidentId);
},
};
const mutations = {
[Type.ADD_TO_PROGRESS]: (state, incidentId) => {
const someArray = [...state.incidentArray];
console.log("incidentId: to be progressed from mutation", incidentId);
const incidentObj = someArray.find(i => i._id === incidentId);
// state.incidentArray.filter(i => i.props.status === "new");
incidentObj.props.status = "inProgress";
console.log("incidentObj found to be in progress:", incidentObj, "and incident arrray:", someArray);
Object.assign(state, { incidentArray: someArray });
},
};
const getters = {
incidentsNew: (state) => { return state.incidentArray.filter(i => i.props.status === "new"); },
incidentsInProgress: (state) => { return state.incidentArray.filter(i => i.props.status === "inProgress"); },
incidentsResolved: (state) => { return state.incidentArray.filter(i => i.props.status === "resolved"); },
incidentTypeConfig: (state) => { return state.incidentTypeConfig; }
};
component.js
// here I am using computed but also incidents is not updating in the component
computed: {
...mapGetters({
incidentsPending: "incident/incidentsNew",
incidentsInProgress: "incident/incidentsInProgress",
incidentsResolved: "incident/incidentsResolved"
}),
I cannot figure out why this is not working:
-spec.js
it.only('passes props to children', () => {
const state = {
company: {
name: 'Company A'
}
},
store = fakeStore(state),
container = <HomePageContainer store={store} />;
const homePageContainer = shallow(container),
homePage = homePageContainer.find(HomePage);
expect(homePage.props.company.name).to.equal(state.company.name)
});
const fakeStore = state => {
return {
default: () => {},
subscribe: () => {},
dispatch: () => {},
getState: () => { return { state };
},
};
};
HomePageContainer.js
import React from 'react';
import { connect } from 'react-redux'
import HomePage from '../../client/components/HomePage';
export const mapStateToProps = state => {
company: state.company
}
export { HomePage }
export default connect(mapStateToProps)(HomePage);
HomePage.js
import React, { Component, PropTypes } from 'react';
export default class HomePage extends Component {
render(){
return (
<div className='homepage'>
{/*{this.props.company.name}*/}
</div>
)
}
}
I'm getting this type error because props.company is undefined so for some reason it's not persisting state to :
TypeError: Cannot read property 'name' of undefined
That error is relating to the assert when it's trying to read expect(homePage.props.company.name)
I notice that when putting a breakpoint inside mapStateToProps, that it's not picking up the state object from the store still for some reason:
I know you can pass just the store via props...and if there's nothing in the context, connect() will be able to find it via the store prop. For example in another test suite, this passes just fine:
it('shallow render container and dive into child', () => {
const container = shallow(<ExampleContainer store={fakeStore({})}/>);
expect(container.find(Example).dive().text()).to.equal('Andy');
});
Problem
At this line you pass the store as a prop into <HomePageContainer>:
// ...
container = <HomePageContainer store={store} />;
// ...
But connect() needs the store via context.
And you must wrap the object you want to return in mapStateToProps in parenthese.
Solution
You can use shallow() to make the store available as a context property.
it.only('passes props to children', () => {
const state = {
company: {
name: 'Company A'
}
};
const store = fakeStore(state);
const homePageContainer = shallow(
<HomePageContainer />,
// Make the store available via context
{ context: { store } }
);
const homePage = homePageContainer.find(HomePage);
expect(homePage.props().company.name).to.equal(state.company.name)
});
Updated mapStateToProps:
export const mapStateToProps = state => ({
company: state.company
});
Found out the problem was really my helper.
This was passing a object with property "state" in it. Not what I want. That would have meant that mapStateToProps would have had to reference the props with state.state.somePropName
const fakeStore = (state) => {
return {
default: () => {},
subscribe: () => {},
dispatch: () => {},
getState: () => { return { state };
},
};
};
changed it to this, and now mapStateToProps is working fine, it's able to access it with the props as the root level of the object literal, the object I passed in from my test:
const fakeStore = (state) => ({
default: () => {},
subscribe: () => {},
dispatch: () => {},
getState: () => ({ ...state })
});