React Redux: Updating Component on Multiple Dependent Props - reactjs

I have a react component, let's call it Documents. In this component I need to load multiple pieces of dependent data using fetch. I am using redux thunk for these async redux actions that perform data fetching.
So the component looks like this:
interface Props {
comments: Entity[];
documents: Entity[];
users: Entity[];
getComments: Function;
getDocuments: Function;
getUsers: Function;
}
export class Documents<Props> {
public async componentDidMount(props: Props){
// is this the right lifecycle method for this since
// it does not really need to change state
const { documents, users, comments, getDocuments, getUsers, getComments} = props;
if(!documents || !documents.length){
await getDocuments();
} else {
await getUsers(documents.map(d => d.username));
}
getComments(documents, users); // dependent upon both users and documents
}
public render() {
// render documents, users, and comments
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Documents);
Here I need to look at the documents before I can load users. And Both users and documents before I can load comments. What's the best approach for handling this in react. My render function will load small sub-components responsible for rendering these data but I don't want them to be connected components.

I think I was able to accomplish it with a functional component/useEffect hooks:
https://codesandbox.io/s/festive-varahamihira-kpssd?file=/src/Documents.js
useEffect(() => {
if (!documents || !documents.length) {
getDocuments();
} else {
getUsers(documents);
}
}, [documents]);
useEffect(() => {
if (users) {
getComments(documents, users);
}
}, [users]);

Related

How to save fetched data from server to component state using redux and redux-thunk?

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...
}

Should I use ComponentDidMount or mergeProps of connect function for data fetching?

I use react with redux and have a component which displays some dataSet fetched from external source. My current code looks like:
const ConnectedComponent = connect(
state => ({
dataSet: state.dataSet
}),
dispatch => ({
loadData: () => {
...
fetch data and dispatch it to the store
...
}
})
)(MyComponent);
class MyComponent extends Component {
...
componentDidMount() {
const { dataSet, loadData } = this.props;
if (!dataSet) {
loadData();
}
}
...
render () {
const { dataSet } = this.props;
if (dataSet) {
// render grid with data
} else {
// render Loading...
}
}
}
The code above works but I'm wondering would it be better to get rid of componentDidMount and just check for data and load it from within connect function? The code might looks like:
const ConnectedComponent = connect(
state => ({
dataSet: state.dataSet
}),
dispatch => ({
dispatch
}),
(stateProps, dispatchProps) => {
const { dataSet } = stateProps;
const { dispatch } = dispatchProps;
if (!dataSet) {
// fetch data asynchronously and dispatch it to the store
}
return {
...stateProps
};
}
)(MyComponent);
class MyComponent extends Component {
render () {
const { dataSet } = this.props;
if (dataSet) {
// render grid with data
} else {
// render Loading...
}
}
}
The latter code looks more attractive to me because of MyComponent becomes simpler. There is no passing of execution first forward from connected component to presentational and then backward, when componentDidMount detects that there are no data ready to display.
Are there some drawbacks of such approach?
PS: I use redux-thunk for an asynchronous fetching.
The second approach, as separation of concepts, is potentially a good solution, because of the layers and responsibility separations - ConnectedComponent is responsible for data fetching, while MyComponent acts as presentational component. Good!
But, dispatching actions in connect mergeProps doesn't seem a good idea, because you introduce side effects.
Also, other drawback I'm seeing, is that the flow of fetching and returning data would be repeated across your different pages (components). Generally speaking the following flow would be repeated:
Connected components call the API for the needed Entities.
While fetching the Entities, we’re showing a Loader.
When the data is available, we pass it to the Presentational components.
Because of the above drawbacks, I can suggest you to organize and reuse your data fetching flow in a HOC.
Here's a pseudo code and flow (taken from my article) that addresses the above drawbacks:
* I've been using it last 1 year and continue stick with it.
Fetcher HOC:
import authorActions from 'actions/author'
const actions = {
'Author': authorActions
}
export default (WrappedComponent, entities) => {
class Fetcher extends React.Component {
// #1. Calls the API for the needed Entities.
componentDidMount () {
this.fetch()
}
fetch () {
const { dispatch } = this.props
entities.forEach(name => dispatch(actions[name].get()))
}
render () {
const { isFetching } = this.props
// #2. While fetching the Entities, we're showing an Loader.
if (isFetching) return <Loader />
return <WrappedComponent {...this.props} />
}
}
const mapStateToProps = state => {
const isFetching = entities
.map(entity => state[entity].isFetching)
.filter(isFetching => isFetching)
return { isFetching: isFetching.length > 0 }
}
return connect(mapStateToProps)(Fetcher)
}
Usage:
const MyComponent = ({ authors }) => <AuthorsList authors={authors} />
const mapStateToProps = state => ({
authors: state.authors
})
const Component = connect(mapStateToProps)(MyComponent)
export default Fetcher(Component, ['Author'])
Here you can read the article and deep dive into its ideas and concepts:
* Fetcher concept is addressed at Lesson #2: Containers on steroids
Long-term React & Redux SPA — Lessons learned

right way to POST data to a server and handle response with redux

I'm very new to react and redux.
Now I want to rewrite my post request with a redux process.
my current request looks like this:
_handleSubmit(event) {
axios
.post('/createUrl', {
url: this.state.url
})
.then((response) => {
this.setState({
shortenInfos: response.data
})
})
.catch((error) => {
console.log(error);
});
event.preventDefault()
}
now I created a store:
export default function url(state = 0, action) {
switch (action.type) {
case 'CREATE_URL':
// maybe axios request?!
return `${action.url}/test`
case 'CREATED_URL':
return `${action.url}/created`
default:
return state
}
}
so where I must use my store.dispatch()? Should I make my _handleSubmit something like this?
_handleSubmit(event) {
axios
.post('/createUrl', {
url: this.state.url
})
.then((response) => {
store.dispatch({
type: 'CREATED_URL',
url: response.data
})
})
.catch((error) => {
console.log(error);
});
event.preventDefault()
}
I think this is wrong? And where I must use mapStateToProps method? Or should I do the axios-request in my CREATE_URL in my reducer?
Introduction
Using React with Redux gives you high freedom on how you can do things. The downside of this is that it can be hard to find out how things should be done properly, mainly because there is no standard or comprehensive guide to the use of the many dependency you need for a properly implemented project. This answer will guide you through the basics with links to references that will help you to find out wheres next and how to deeper your knowledge.
Reducer
Reducers should be pure, meaning that they have no side effects (like making axios requests) and they should always return a new object/array/value instead of changing the previous state. It is also a good practice to use action types as constants. You can place action types wherever you want, but for simplicity I will put them into the reducer's file, but there are better ways to organize them like using ducks.
export const CREATED_URL = 'CREATE_URL';
export default const url = (state = '', action) => {
switch (action.type) {
case CREATED_URL:
return action.url;
default:
return state;
}
};
Asynchronous actions
Everything that causes a side effect should be an action, so XHR should happen there. Because XHR should be asynchronous it is recommended to use a middleware: redux-thunk and redux-saga are two popular solutions. I will go with thunk so install it first.
First (because const has temporal dead zone) you need an action that will "load" the result of the XHR to the store:
import { CREATED_URL } from './reducer';
const createdUrl = url => ({
type: CREATED_URL,
url, // ES6 trailing comma for prettier git diffs
});
Then you can create the action that will fire the XHR, wait for the response then load it to the store using the action created previously. We need to return a function that will receive dispatch as the parameter. This technique is used in functional programming and is called currying.
export const createUrl = url => dispatch => { // with only 1 parameter the parentheses can be omited
axios
.post('/createUrl', { url }) // ES6 Shorthand property name in { url }
.then(response => {
dispatch(createdUrl({
url: response.data,
})
})
.catch(error => {
// #TODO dispatch an action that will show a message
// notifying the user that the request failed
console.log(error);
});
}
Usage in the React component.
Preparation
For ease of use, you need to connect your React component with Redux. react-redux comes to the rescue. Read the API documentation and add the <Provider> component to the root of your React component tree.
Now, in the top of your React component's file, import all the necessary stuff:
import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { createUrl } from './reducer';
mapStateToProps and mapDispatchToProps
Then create the two helper functions for connect:
const mapStateToProps = store => ({ url: store.url })
const mapDispatchToProps = dispatch => bindActionCreators({ createUrl }, dispatch)
With the help of mapStateToProps you can subscribe to store updates and inject the important parts of the Redux store to your components props. mapStateToProps should return an object that will be merged to the component's props. Usually we just do something like store => store.url but because our example is so simple that the reducer returns a plain string instead of something more complex in an object, we need to wrap that string into an object over here.
mapDispatchToProps with the help of bindActionCreators will inject the passed actions to the component's props so we can call and pass them down to subcomponents with ease: this.props.createUrl().
The component itself
Now we can create the component itself. I will use an ES6 class to show an example with componentDidMount, but if you don't need that and you have a stateless component, you can go with a function too.
class Example extends React.Component {
componentDidMount() {
// If you need to load data from a remote endpoint place the action call here, like so:
// this.props.createUrl('your-url');
}
render() {
return (
<div>
<div>URL injected from the store, automatically refreshed on change: {this.props.url}</div>
<div onClick={event => {this.props.createUrl('your-url');}}>Click me to fetch URL</div>
</div>
)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Example)

Correct way to pre-load component data in react+redux

I do not know the correct way to pre-load data from API for a component to use.
I have written a stateless component which should render the data:
import React, { PropTypes } from 'react';
const DepartmentsList = ({ departments }) => {
const listItems = departments.map((department) => (
<li>{department.title}</li>
));
return (
<ul>
{listItems}
</ul>
);
};
DepartmentsList.propTypes = {
departments: PropTypes.array.isRequired
};
export default DepartmentsList;
And I have an action which will retreive data from the API:
import { getDepartments } from '../api/timetable';
export const REQUEST_DEPARTMENTS = 'REQUEST_DEPARTMENTS';
export const RECEIVE_DEPARTMENTS = 'RECEIVE_DEPARTMENTS';
const requestDepartments = () => ({ type: REQUEST_DEPARTMENTS });
const receiveDepartments = (departments) => ({ type: RECEIVE_DEPARTMENTS, departments });
export function fetchDepartments() {
return dispatch => {
dispatch(requestDepartments);
getDepartments()
.then(departments => dispatch(
receiveDepartments(departments)
))
.catch(console.log);
};
}
Now I think I have a few options to preload departments that are required for the list. I could use redux-thunk and mapDispatchToProps to inject fetchDepartments to the stateless component and implement componentWillMount or similar lifecycle method, to load data - but then I don't need to pass the list via props, as the component would always load data for himself, and I don't want that, because whenever a new component is created the data is fetched from api instead of store...
Another advice I've seen is to use getComponent function from react-router, and retreive all data before returning the component, however, I am not sure if it's the correct redux way, as I don't see how to use redux-thunk there, and logic kind of seems littered all accross the files, when it's the data required for only one component.
This leaves me with the only seemingly ok option to load data in container component's lifecycle methods, but I want to know what is considered the best practice for what I want to do.
The most 'redux-like' way of handling the pre-loading of data would be to fire off the asynchronous action in the lifecycle method (probably componentWillMount) of a Higher Order Component that wraps your app. However, you will not use the results of the API call directly in that component - it needs to be handled with a reducer that puts it into your app store. This will require you to use some sort of a thunk middleware to handle the asynchronous action. Then you will use mapStateToProps to simply pass it down to the component that renders the data.
Higher Order Component:
const mapStateToProps = (state) => {
return {
departments: state.departments
};
}
const mapDispatchToProps = (dispatch) => {
return bindActionCreators({
getDepartments: actionCreators.fetchDepartments
});
}
class App extends Component {
componentWillMount() {
this.props.getDepartments();
}
render() {
return <DepartmentsList departments={this.props.departments} />
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App);
reducers:
export function departments(state = [], action) {
switch(action.type) {
case 'RECEIVE_DEPARTMENTS':
return action.departments;
}
}

Need individual entry with Redux and Router

I'm using ReactJS, Redux (with server-side rendering) and react-router-redux as set up here and am getting a little thrown by how routes work with the rest of the redux state and actions.
For example, I have a members component with the route /members:
class Members extends Component {
static need = [
fetchMembers
]
render() {
...
the static need array specifies an action that populates an array on the state that is then mapped to the component props. That much works.
But then I have an individual member component with the route members/:memberId. How do I load that individual member in a way that works both client- and server-side.
What I'm doing now is the same:
class Member extends Component {
static need = [
fetchMembers
]
render() {
...
but then map just the single member
function mapStateToProps(state, ownProps) {
return {
member: state.member.members.find(member => member.id == ownProps.params.memberId),
};
}
This works but is obviously wrong. So the question is two-fold:
When the user clicks the router Link that has a query param (:memberId), how do I use that router param to query a specific document (assume a mongo database). Do I somehow trigger a separate action that populates an active member field on the redux state? Where does this happen, in the route component's componentDidMount?
How does this work with server-side rendering?
I’ve had the same question and seemed to find a way that works pretty well with my setup. I use Node, Express, React, React Router, Redux and Redux Thunk.
1) It really depends on where your data is. If the data needed for /member/:memberId is already in state (e.g. from an earlier call) you could theoretically filter through what you already have when componentDidMount is fired.
However, I'd prefer to keep things separate simply to avoid headaches. Starting to use one data source for multiple destinations/purposes throughout your app might give you long days down the road (e.g. when Component A needs more/less properties about the member than Component B or when Component A needs properties in a different format than Component B etc.).
This decision should of course be based on your use-case but due to the cost of API calls nowadays I wouldn't be afraid (at all) to make one when someone navigates to /member/:memberId.
2) I’ll answer with a simplified version of my typical setup:
Whenever a request comes through, I have this fella handle it.
// Imports and other jazz up here
app.use((req, res) => {
const store = configureStore({});
const routes = createRoutes(store);
match({ routes, location: req.url }, (error, redirectLocation, renderProps) => {
if (error) {
res.status(500).send(error.message);
} else if (redirectLocation) {
res.redirect(302, redirectLocation.pathname + redirectLocation.search);
} else if (renderProps) {
const fetchedData = renderProps.components
.filter(component => component.fetchData)
.map(component => component.fetchData(store, renderProps.params));
Promise.all(fetchedData).then(() => {
const body = renderToString(
<Provider store={store}>
<RouterContext {...renderProps} />
</Provider>
);
res.status(200).send(`<!doctype html>${renderToStaticMarkup(
<Html
body={body}
state={store.getState()}
/>)
}`);
});
} else {
res.status(404).send('Not found');
}
});
});
It’ll look for fetchData on the components that are about to be rendered, and make sure we have the data before we send anything to the client.
On each and every route, I have a Container. The Container’s sole purpose is to gather the data needed for that route. As you’ve touched upon this can happen server-side (fetchData in my case) or client-side (componentDidMount in my case). A typical Container of mine looks like this:
// Imports up here
class Container extends Component {
static fetchData(store, params) {
const categories = store.dispatch(getCategories());
return Promise.all([categories]);
}
componentDidMount() {
this.props.dispatch(getCategoriesIfNeeded());
}
render() {
return this.props.categories.length ? (
// Render categories
) : null;
}
}
Container.propTypes = {
categories: PropTypes.array.isRequired,
dispatch: PropTypes.func.isRequired,
params: PropTypes.object.isRequired,
};
function mapStateToProps(state) {
return {
categories: state.categories,
};
}
export default connect(mapStateToProps)(Container);
In the Container above I’m using getCategories and getCategoriesIfNeeded to make sure that I have the data needed for the route. getCategories is only called server-side, and getCategoriesIfNeeded is only called client-side.
Note that I have params available for both fetchData and componentDidMount (passed from connect()), which I could potentially use to extract something like :memberId.
The two functions used to fetch data above are listed below:
// Using this for structure of reducers etc.:
// https://github.com/erikras/ducks-modular-redux
//
// actionTypes object and reducer up here
export function getCategories() {
return (dispatch, getState) => {
dispatch({
type: actionTypes.GET_REQUEST,
});
return fetch('/api/categories').then(res => {
return !res.error ? dispatch({
error: null,
payload: res.body,
type: actionTypes.GET_COMPLETE,
}) : dispatch({
error: res.error,
payload: null,
type: actionTypes.GET_ERROR,
});
});
};
}
export function getCategoriesIfNeeded() {
return (dispatch, getState) => {
return getState().categories.length ? dispatch(getCategories()) : Promise.resolve();
};
}
As displayed above I have both dispatch and getState available thanks to Redux Thunk - that handles my promises too - which gives me freedom use the data I already have, request new data and do multiple updates of my reducer.
I hope this was enough to get you moving. If not don't hesitate to ask for further explanation :)
The answer, it turns out, was pretty simple. The implementation taken from Isomorphic Redux App ties the need static property on a component back to the router by passing the routes query params into the action creator.
So for the route:
items/:id
you'd use a component like
class Item extends Component {
static need = [
fetchItem
]
render() {
specifying that it needs the fetchItem action. That action is passed the route's query params, which you can use like
export function fetchItem({id}) {
let req = ...
return {
type: types.GET_ITEM,
promise: req
};
}
For a more detailed explanation about why this work, read marcfalk's answers, which describes a very similar approach.

Resources