I have a react-redux app with 3 reducers: clientPrivileges, userFilter and userData acting on my store.
A table component is used to present the data for each user and a drop down component is used to filter this table for specific users. When the drop down is selected for a specific user I need to call the backend to retrieve the data. This is the action associated with this:
selectUser(userId, dispatch) {
api.getUserData(userId, accessTypeId, (data) => {
dispatch(userActions.update(data));
});
return{
type: selectUser,
userId
}
}
However you can see that I have an argument called accessTypeId which needs to be sent to the backend as well as the userId. This value has been set in the store using the clientPrivileges reducer on login to the app.
I can't see any other way other than setting accessTypeId as a prop for the drop-down component in its mapStateToProps. And then in the component itself:
this.props.users.map(u => {
function onClick()
{
selectEndUserGroup(u.id, this.props.accessTypeId);
}
return <div id={"filter_group_"+u.id} key={u.id} onClick={onClick}>{name}</div>
But now I've destroyed my generic drop-down component with an accessTypeId property. How should I be doing this?
If I'm understanding correctly, you want your action to have access to a value stored in the Redux state, yes?
Redux-Thunk handles this nicely. The code would look something like this;
selectUser(userId) {
return function(dispatch, getState){
var accessTypeId = getState().[PATH TO REDUX STATE ELEMENT]
api.getUserData(userId, accessTypeId, (data) => {
dispatch(userActions.update(data));
});
dispatch({
type: selectUser,
userId
})
}
}
Related
I have a react-redux app and one of the actions triggered requires checking the presence of some data in the store. If the data is not present I want to discard the action and don't want to proceed, but if the the data is present we want to fire another action that updates the store.
I was wondering what would be the correct way to do that? The code snippet/pseudo code below mimics something similar.
<MyComponent onClick={onClickHandler}/>
onClickHandler = () => {
if(checkIfDatapresentInStore) {
// anActionHandler();
} else {
anotherActionHandler();
}
}
//Redux-store
store = {
dataPresentInStore: true
}
Thanks
You can use getState store method. It will return current state present in the store. Then you just need to check the state you are looking for and trigger actions based on that.
To elaborate on Sunny's answer, this is something that's possible to do either within the action creator, or within the component's handler function. It's really up to you if you want to make the dataPresent state available to your component or not.
Via action creator in actions.js:
// Note: the second argument to action callback is
// a function that returns the whole store
const conditionalAction = () => (dispatch, getState) => {
// Retrieve the whole store object and check what you need from it
const { dataPresent } = getState();
// Conditionally dispatch an action
if (dataPresent) {
dispatch({ type: "MAIN_ACTION" });
} else {
dispatch({ type: "OTHER_ACTION" });
}
}
OR via your example in MyComponent.js.
After some researches, I found some questions on stackoverflow about what I am trying to achieve, however, I don't feel that these questions and their answers gives me the "answers" or the "directions" i am looking for..
Note: I am pretty new to react even if I already made 2 projects and implemented redux into one of them. However, I ain't new at all in C# or in Go, even less in C. Based on my experience, I am just used to some architectures and I would like to reproduce one of them.
Here is a pretyy good schema from a similar question of mine:
Situation:
So let say I have pages that contains Components. I want these pages/compoments to display some stuff. One of my functionnality is to discover a map and for that, when the client moves, he gets new parts from my API. However, I don't wanna ask the server to give me the new parts and the ones I discovered already.
My idea about it would be to use a service MapService.js. This one would just store the discovered pieces of the map discovered and ask the server automatically about the new ones, and of course, store the new ones (concat).
However, I have to be logged for this, so I would like an ApiService.js that would store my authentication data and automatically put them in each of my requests.
Based on what I said, we would have something as:
Page -> Component -> Service -> API
From this, the API response would be gotten by my service, handled, then returned to the component. Handled means (data added to the previous then all returned)
I saw on internet one question that was referring "MVCS" (Model View Controller Service) pattern and I think I am looking for something as but I am not sure about how to implement it in ReactJs.
Redux seems to be something that you put all around and everywhere in your solution. What I would like is to use it as a "repository" let say, to be able to manage it from a service and not from the component itself. However, a service should be a single instance shared across the app and I don't know if something such as dependency injection could be the solution in ReactJS
Feel free to ask any edit if you need more details :)
Thanks for your help !
Here is a minimal example of Redux middleware usage. Usually, redux devs are using libraries (that give you a middleware) to have access to more appropriate APIs.
Redux middleware are chained, so each middleware can call the next middleware. The first middleware of the chain is called every time dispatch function (you can have it from react-redux connect) is called. In a middleware, if there is no next middleware it is the reducers that will be called. The next middleware can be call asynchronously after receiving an action. (Redux docs will still be better than my explainations).
In my example there is a catService that provide function that call rest API. Your services can be anything (a Class instance or a singleton for example). Usually in React/Redux stack, devs don't use object oriented development.
If a component dispatch getCat(123), the catMiddleware will be called (synchronously). Then requestGetCat will be called with the id 123. When the promise returned by requestGetCat will be resolved a setCat action will be send through the reducers to update the redux state. Once the redux state is done, the component listening for cats items object will be update too (triggering a rerender).
That can look very complexe, but in fact, it is very scalable and convenient.
// catService.js
// return a promise that return a cat object
const requestGetCat = id =>
fetch(`www.catcat.com/api/cat/${id}`)
.then(response => response.json())
// catTypes.js
export const GET_CAT = 'GET_CAT'
export const SET_CAT = 'SET_CAT'
// catActions.js
export const getCat = id => ({
type: GET_CAT,
id
})
export const setCat = (cat, id) => ({
type: SET_CAT,
id,
cat
})
// catReducer.js
const initialState = {
items: {}
}
const catReducer = (state = initialState, action) => {
if (action.type === SET_CAT) {
return {
items: {
...state.items,
[action.id]: action.cat
}
}
}
}
// catMiddleware.js
const handleGetCat = (next, action) => {
requestGetCat(action.id)
.then(cat => next(setCat(cat, action.id)))
// after retrieving the cat send an action to the reducers (or next middleware if it exist)
}
const actionHandlers = {
[GET_CAT]: handleGetCat
}
// receive every actions passing by redux (if not blocked)
// store: { dispatch, getState }
// next: next middleware or reducers (that set redux state)
// action: a redux action (dispatched) with at least type property
const catMiddleware = store => next => action => {
const handler = actionHandlers[action.type]
if (handler) {
handler(next, action)
} else {
// passing the action to the next middleware (or reducer - when there is no next middleware)
next(action)
}
}
// you have to apply your middleware
// and your reducer (see redux doc)
This one would just store the discovered pieces of the map discovered and ask the server automatically about the new ones, and of course, store the new ones
This is something I've wanted to do in the past, but never implemented a solution for.
The issue is that you essentially want to "cross the streams"..
In Redux there are two separate streams, ie dispatch an action to update the store, and read data from the store. Each of these are executed separately from a component. Combined, they can be used in a cycle by calling an action to load data into the store which triggers an update of the component which then reads from the store.
Basically you can't have non-component code that reads from the store, and if the data is missing, fires an action to load the data, then returns the data.
Thinking about it now, I'm wondering if the way to do this without adding logic to your view component is to wrap it in a component (HOC) that provides the logic.
The HOC will check the state for the location specified in the props. If it doesn't find it, it will dispatch an action to fetch it and render a loading display. When the state is updated with the new location it will update and render the wrapped component.
You could optionally always render the wrapped component and have it cope with the missing location until it is updated with the location set..
untested brain-dump below
loader HOC:
import React, { useEffect } from "react";
import actions from "./actions";
function withLocationLoader(Component) {
const Wrapper = function ({ location, locations, loadLocation, ...props }) {
useEffect(() => {
if (!locations[location]) {
loadLocation(location);
}
}, [locations]);
if (locations[location]) {
return <Component locations={locations} {...props} />;
}
return <div>Loading...</div>;
}
const mapStateToProps = (state, ownProps) => {
return { locations: state.locations };
};
const mapActionsToProps = {
loadLocation: actions.loadLocation,
};
return connect(
mapStateToProps,
mapActionsToProps
)(Wrapper);
}
export { withLoader };
component:
function MyBareComponent({ locations }) {
return <div>{JSON.stringify(locations)}</div>;
}
const MyComponent = withLocationLoader(MyBareComponent);
export { MyComponent };
actions: (utilising redux-thunk middleware)
function setLocation(location, data) {
return { type: "SET_LOCATION", payload: { location, data } };
}
export function loadLocation(location) {
return dispatch =>
Promise.resolve({ geoData: "" }) // mock api request
.then(data => dispatch(setLocation(location, data)));
}
In my react app I have component named profile, and I am fetching data from server and showing it inside that component. I am using redux and redux-thunk along with axios. With help of mapDispatchToProps function, i am calling redux action for fetching that data when component is mounted and saving it to redux state. After that, using mapStateToProps function i am showing that data on the screen via props. That works fine. Now I want to have possibility to edit, for example, first name of that user. To accomplish that i need to save that data to component state when data is fetched from server, and then when text field is changed, component state also needs to be changed. Don't know how to save data to component sate, immediately after it is fetched.
Simplified code:
const mapStateToProps = (state) => {
return {
user: state.user
}
}
const mapDispatchToProps = (dispatch) => {
return {
getUserData: () => dispatch(userActions.getUserData())
}
}
class Profile extends Component {
state:{
user: {}
}
componentDidMount (){
this.props.getUserData()
// when data is saved to redux state i need to save it to component state
}
editTextField = () => {
this.setState({
[e.target.id]: e.target.value
})
};
render(){
const { user } = this.props;
return(
<TextField id="firstName"
value={user.firstName}
onChange={this.editTextField}
/>
)
}
}
You can use componentDidUpdate for that or give a callback function to your action.
I will show both.
First lets see componentDidUpdate,
Here you can compare your previous data and your present data, and if there is some change, you can set your state, for example if you data is an array.
state = {
data: []
}
then inside your componentDidUpdate
componentDidUpdate(prevProps, prevState) {
if(prevProps.data.length !== this.props.data.length) {
// update your state, in your case you just need userData, so you
// can compare something like name or something else, but still
// for better equality check, you can use lodash, it will also check for objects,
this.setState({ data: this.props.data});
}
}
_.isEqual(a, b); // returns false if different
This was one solution, another solution is to pass a call back funtion to your action,
lets say you call this.props.getData()
you can do something like this
this.props.getData((data) => {
this.setState({ data });
})
here you pass your data from redux action to your state.
your redux action would be something like this.
export const getData = (done) => async dispatch => {
const data = await getSomeData(); // or api call
// when you dispatch your action, also call your done
done(data);
}
If you are using React 16.0+, you can use the static method getDerivedStateFromProps. You can read about it react docs.
Using your example:
class Profile extends Component {
// other methods here ...
static getDerivedStateFromProps(props) {
return {
user: props.user
}
}
// other methods here...
}
technologies: using react, Redux, Redux Form (FieldsArray), MongoDB.
I have a list of entities and I want to do the following:
I want to create an entity -> get back from server an entity with _id --> update store with then entity and the _id.
How do I do that?
actions.js
export function createEntity(entity) {
return function (dispatch) {
dispatch(createEntityStart());
return axios.post(
'http://localhost:3000/api/users',
entity,
)
.then(function (response) {
dispatch(createEntitySuccess(response.entityWithId));
}).catch(function (response) {
dispatch(createEntityError(response.data));
});
};
}
I have done the fields.push({}) - Got a new entity in the component.
Now I would like to post the new entity which follow a returned entity (with id).
I now need to save the entity with Id somewhere in the store.
How it should be done?
I got in the store:
form
MyFormName
values
initial
registeredFields
If you are looking to store it in redux-form's state, there are a couple options available. You can look at using redux-form's Action Creators to manipulate its state. Or you can add a plugin into its form reducer (this is a super simplified example):
const reducers = {
form: formReducer.plugin({
entityForm: (state, action) => {
switch(action.type) {
case CREATE_ENTITY:
return {
...state,
values: {
action.payload
},
registeredFields: {
...state.registeredFields,
}
}
default:
return state
}
}
})
}
You can find more details about redux-form plugins at http://redux-form.com/7.0.3/docs/api/ReducerPlugin.md/
Another option, if you don't want to save it in redux-form's state, is to just create your own state object within redux that stores the entity information.
Note: I am using Reflux as my Flux library, so the samples will use its syntax. The question applies to Flux in general, however.
In my sample Flux application I have a productStore.js file that holds the state of the products in the system and listens to various product management actions, e.g REMOVE_PRODUCT, ADD_PRODUCT.
Here is a sample data in the store:
{
products: [
{
productName: "A"
},
{
productName: "B"
}
]
}
Now I would like to add a REFRESH_PRODUCTS action to a component.
The invocation looks like that:
component.jsx
onRefresh(e) {
actions.refreshProducts();
}
Since refreshing the products is an async operation, I would like to show the spinner while it goes on and show an error if it fails. The obvious, Flux way, would be to add the loading state and the resulting error, if such happens, to the store, like so:
productStore.js
onRefreshProducts() {
logger.info("Logging in:", email);
this.storeData.inProgress = true;
this.storeData.error = null;
this.trigger(this.data);
api.getProducts()
.then((response) => {
this.storeData.products = response.data.products;
})
.catch((response) => {
this.storeData.error = error;
})
.then(() => {
this.storeData.inProgress = false;
this.trigger(this.data);
});
}
And now the store of the data becomes dirty with various flags:
{
inProgress: false,
error: null,
products: [
{
productName: "A"
},
{
productName: "B"
}
]
}
This kind of state would be perfectly fine for me if multiple components would need to see the progress of products loading, or refresh failing, but in case, no other components needs that kind of information. So it feels I am putting private data to global state without a good reason.
I would like to be able to do something like that:
component.jsx - BAD CODE
onRefresh(e) {
this.setState({error: false, inProgress: true});
actions.refreshProducts()
.catch(function(err) {
this.setState({error: err});
})
.then(function() {
this.setState({inProgress: false});
});
}
Then I could keep the code of store clean. However, I have no obvious way to do that - Actions, by design, create a separation that don't allow to return data from actions.
What's the proper way to do it? How can I do private spinners/errors/etc while keeping the related data out of global state?
Here is one solution I thought of while writing this question:
Create a new action on the store that allows to update the product data by argument, e.g: refreshProductFromData
Call the API directly from the component
Manipulate the spinners/error handling in the component
Pass the data retrieved from API to the store via the new action
Like so:
component.jsx
onRefresh(e) {
this.setState({error: false, inProgress: true});
api.getProducts()
.then(function(data) {
actions.refreshProductFromData(response.data.products);
});
.catch(function(err) {
this.setState({error: err});
})
.then(function() {
this.setState({inProgress: false});
});
}
Not sure if it is the right/best solution or not however.
I found your post because I had the same question. I think I'm going to structure mine like this, keeping everything decoupled and communicating via actions. I like to keep the component and store ignorant with regards to the API.
The Product Actions know how to interact with the API to complete the requested action.
The Product Store listens to the Completed action to update its internal state.
The LoadingActions manage the spinner state and are asked to show/hide the spinner when API calls are initiated.
I have a Spinner component that listens to LoadingActions and updates its state to show/hide the spinner.
Component:
actions.refresh();
(Product) Actions:
onRefresh: function () {
LoadingActions.showSpinner();
api.loadProducts().then(this.completed, this.failed).finally(LoadingActions.hideSpinner);
}
Loading Actions:
onShowSpinner: function () { ... }
onHideSpinner: function () { ... }
Store:
onRefreshCompleted: function (data) {
this.products = data;
this.trigger();
}