I'm a bit confused and would love an answer that will help me to clear my thoughts.
Let's say I have a backend (nodejs, express etc..) where I store my users and their data, and sometimes I wanna fetch data from the backend, such as the user info after he logs in, or a list of products and save them in the state.
My approach so far and what I've seen, I fetch the data before the component loads and dispatch an action with the data from the response.
But I recently started digging a bit about this and I saw react-thunk library which I knew earlier and started to wonder if what is the best practice of fetching from backend/API?
Has React Hooks change anything about this topic? Is it important to know this?
I feel a bit dumb but couldn't find an article or video that talks exactly about this topic :)
To do this best practice, use the following method:
I used some packages and patterns for best practice:
redux-logger for log actions and states in console of browser.
reselect Selectors can compute derived data, allowing Redux to
store the minimal possible state and etc.
redux-thunk Thunks are the recommended middleware for basic
Redux side effects logic, including complex synchronous logic that
needs access to the store, and simple async logic like AJAX requests
and etc.
axios for work with api (Promise based HTTP client for the
browser and node.js)
create a directory by name redux or any name of you like in src folder and
then create two files store.js and rootReducer.js in redux directory. We assume fetch products from API.
To do this:
Create a new directory by name product in redux directory and then
create four files by names product.types.js, product.actions.js,
product.reducer.js, product.selector.js in redux/product directory
The structure of the project should be as follows
...
src
App.js
redux
product
product.types.js
product.actions.js
product.reducer.js
rootReducer.js
store.js
Index.js
package.json
...
store.js
In this file we do the redux configuration
// redux/store.js:
import { createStore, applyMiddleware } from "redux";
import logger from "redux-logger";
import thunk from "redux-thunk";
import rootReducer from "./root-reducer";
const middlewares = [logger, thunk];
export const store = createStore(rootReducer, applyMiddleware(...middlewares));
rootReducer.js
The combineReducers helper function turns an object whose values are
different reducing functions into a single reducing function you can
pass to createStore.
// redux/rootReducer.js
import { combineReducers } from "redux";
import productReducer from "./product/product.reducer";
const rootReducer = combineReducers({
shop: productReducer,
});
export default rootReducer;
product.types.js
In this file we define constants for manage types of actions.
export const ShopActionTypes = {
FETCH_PRODUCTS_START: "FETCH_PRODUCTS_START",
FETCH_PRODUCTS_SUCCESS: "FETCH_PRODUCTS_SUCCESS",
FETCH_PRODUCTS_FAILURE: "FETCH_PRODUCTS_FAILURE"
};
product.actions.js
In this file we create action creators for handle actions.
// redux/product/product.actions.js
import { ShopActionTypes } from "./product.types";
import axios from "axios";
export const fetchProductsStart = () => ({
type: ShopActionTypes.FETCH_PRODUCTS_START
});
export const fetchProductsSuccess = products => ({
type: ShopActionTypes.FETCH_PRODUCTS_SUCCESS,
payload: products
});
export const fetchProductsFailure = error => ({
type: ShopActionTypes.FETCH_PRODUCTS_FAILURE,
payload: error
});
export const fetchProductsStartAsync = () => {
return dispatch => {
dispatch(fetchProductsStart());
axios
.get(url)
.then(response => dispatch(fetchProductsSuccess(response.data.data)))
.catch(error => dispatch(fetchProductsFailure(error)));
};
};
product.reducer.js
In this file we create productReducer function for handle actions.
import { ShopActionTypes } from "./product.types";
const INITIAL_STATE = {
products: [],
isFetching: false,
errorMessage: undefined,
};
const productReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case ShopActionTypes.FETCH_PRODUCTS_START:
return {
...state,
isFetching: true
};
case ShopActionTypes.FETCH_PRODUCTS_SUCCESS:
return {
...state,
products: action.payload,
isFetching: false
};
case ShopActionTypes.FETCH_PRODUCTS_FAILURE:
return {
...state,
isFetching: false,
errorMessage: action.payload
};
default:
return state;
}
};
export default productReducer;
product.selector.js
In this file we select products and isFetching from shop state.
import { createSelector } from "reselect";
const selectShop = state => state.shop;
export const selectProducts = createSelector(
[selectShop],
shop => shop.products
);
export const selectIsProductsFetching = createSelector(
[selectShop],
shop => shop.isFetching
);
Index.js
In this file wrapped whole app and components with Provider for access child components to the store and states.
// src/Index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { Provider } from "react-redux";
import { store } from "./redux/store";
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
App.js class component
In this file we do connect to the store and states with class component
// src/App.js
import React, { Component } from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import {
selectIsProductsFetching,
selectProducts
} from "./redux/product/product.selectors";
import { fetchProductsStartAsync } from "./redux/product/product.actions";
class App extends Component {
componentDidMount() {
const { fetchProductsStartAsync } = this.props;
fetchProductsStartAsync();
}
render() {
const { products, isProductsFetching } = this.props;
console.log('products', products);
console.log('isProductsFetching', isProductsFetching);
return (
<div className="App">Please see console in browser</div>
);
}
}
const mapStateToProps = createStructuredSelector({
products: selectProducts,
isProductsFetching: selectIsProductsFetching,
});
const mapDispatchToProps = dispatch => ({
fetchProductsStartAsync: () => dispatch(fetchProductsStartAsync())
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(App);
or App.js with functional component ( useEffect hook )
In this file we do connect to the store and states with functional component
// src/App.js
import React, { Component, useEffect } from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import {
selectIsProductsFetching,
selectProducts
} from "./redux/product/product.selectors";
import { fetchProductsStartAsync } from "./redux/product/product.actions";
const App = ({ fetchProductsStartAsync, products, isProductsFetching}) => {
useEffect(() => {
fetchProductsStartAsync();
},[]);
console.log('products', products);
console.log('isProductsFetching', isProductsFetching);
return (
<div className="App">Please see console in browser</div>
);
}
const mapStateToProps = createStructuredSelector({
products: selectProducts,
isProductsFetching: selectIsProductsFetching,
});
const mapDispatchToProps = dispatch => ({
fetchProductsStartAsync: () => dispatch(fetchProductsStartAsync())
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(App);
Related
I'm doing an API call to my server using Redux, but I'm unsure what the "best practices" are for doing so.
When simply using React, one common way of doing API calls is that you create a folder called for example hooks, where you create your own custom hooks to make the API calls that you want.
However, when it comes to using Redux with React, it is not really clear to me what the best method of structuring your client and API calls are, in order to make it more readable and easy to scale in the future.
Questions
So, what are the best practises for making API calls with Redux in React to increase readability and scaleability?
Second question, what are the best practises to use API calls in a functional component in React with React Redux?
Here below are the current structure of a project, where I make a simple GET request using Redux to the server, which respons with the JSON Placeholder /post.
Starting off with the file structure, to give a better understanding of where everything is located.
Files related to the API call
index.js (root)
import App from "./App";
import reducers from "./reducers";
ReactDOM.render(
<Provider store={createStore(reducers, applyMiddleware(thunk))}>
<App />
</Provider>,
document.querySelector("#root")
);
index.js (reducers)
import postReducer from "./postReducer";
export default combineReducers({
posts: postReducer,
});
postReducer.js (reducers)
import _ from "lodash";
import { GET_POSTS } from "../actions/types";
function postReducer(state = {}, action) {
switch (action.type) {
case GET_POSTS:
return { ...state, ..._.mapKeys(action.payload, "id") };
default:
return state;
}
}
export default postReducer;
requestPosts.js (apis)
import axios from "axios";
const API_URL = "http://localhost:8000";
async function httpGetAllPosts() {
const response = await axios.get(`${API_URL}/posts`);
return response.data;
}
export { httpGetAllPosts};
types.js (actions)
export const GET_POSTS = "GET_POSTS";
index.js (actions)
import { httpGetAllPosts } from "../apis/requestPosts";
import { GET_POSTS } from "./types";
const getAllPosts = () => async (dispatch) => {
const response = await httpGetAllPosts();
dispatch({ type: GET_POSTS, payload: response });
};
export { getAllPosts };
Moving on to the second question. What are the best practises to use API calls in a functional component in React with React Redux?
Here is how I used it in a functional component. Is this the ideal way of doing it? I think the useEffect looks a bit weird, when all it does is call a function and has a dependancy related to that function.
import React, { useEffect } from "react";
import { connect } from "react-redux";
import { getAllPosts } from "../actions";
import { Card } from "react-bootstrap";
const Posts = ({ getAllPosts, posts }) => {
useEffect(() => {
getAllPosts();
}, [getAllPosts]);
return (
<div>
{posts.map((post) => {
return (
<Card style={{ width: "45rem", marginBottom: "1rem" }} key={post.id}>
<Card.Body>
<Card.Title>{post.title}</Card.Title>
<Card.Text>{post.body}</Card.Text>
</Card.Body>
</Card>
);
})}
</div>
);
};
const mapStateToProps = (state, ownProps) => {
return { posts: Object.values(state.posts) };
};
export default connect(mapStateToProps, { getAllPosts })(Posts);
mern stack, using a class component I call Landing I use the componentDidMount method.
on form submit axios is using the get method to return my user object. I am then dispacting my user object with an exported function to my store. Recieving this error:
Actions must be plain objects. Instead, the actual type was: 'Promise'. You may need to add middleware to your store setup to handle dispatching other values, such as 'redux-thunk' to handle dispatching functions.
I export default the Landing component by executing connect and passing the action I had exported followed by the execution of Landing.
The index.js file is currently utilizing redux-thunk middleware.
My goal is to update the state of user so all components immediately display the content that is changing on the form submit.
App.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import { reducers } from './reducers';
import App from './components/App';
const store = createStore(reducers, {}, compose(applyMiddleware(thunk)));
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root'),
);
Landing.js
import React from 'react';
import { io } from 'socket.io-client';
import _ from 'underscore';
import { connect } from 'react-redux'
import './styles.css';
import { bid } from '../../actions/auction';
import { bidPlaced, loggedUser } from '../../api/index';
import { CONNECTION_PORT } from '../../config';
class Landing extends React.Component {
constructor(props) {
super(props);
this.state = {
user: ''
}
componentDidMount() {
this.setState({user: JSON.parse(localStorage.getItem('profile))}) //sets the logged in user to state
}
handleFormSubmit = async (e) => { //This function is wrapped in tryCatch in my app
e.preventDefault()
//Storing user data in an object
const userData = {email: this.state.user.result.email, id: this.state.user.result._id}
const response = await loggedUser(userData)
//which returns a promise, which does currently hold the updated userModel
this.props.bid(response)
}
render (
<div>
<form onSubmit={handleFormSubmit}>
<input value={'all the user information'}>
<button type="submit">Click Me</button>
<form/>
)
}
export default connect(null, { bid })(Landing);
in my actions directory:
auction.js
export const bid = async (user) => ({
type: EDIT,
data: user
});
reducers
bid.js
import * as actionType from '../constants/actionTypes';
let payload = null
if (localStorage.getItem('profile')) payload = localStorage.getItem('profile')
const bidReducer = (state = { authData: payload }, action) => {
switch (action.type) {
case actionType.EDIT:
localStorage.setItem('profile', JSON.stringify({ ...action?.data }));
return { ...state, authData: action.data, loading: false, errors: null };
default:
return state;
}
};
export default bidReducer;
Just remove async in the bid
export const bid = (user) => ({
type: EDIT,
data: user
});
I am trying to use React with Redux for the frontend part with django rest framework in the backend. Got the issue getState in Provider tag in App component because of issue in store. And when i try to use the map function in the Words.js, I get error of undefined use of map. And I believe this is because of value of the array is null. Hence to fixed this error of getState.
Got this error even on including the store in Provider of App component when a reducers was not defined.
When I load a static array it does get rendered properly in the specific component.
This is Redux Store in the filename:store.js
import { createStore, applyMiddleware } from "redux";
import { composeWithDevTools } from 'redux-devtools-extension';
import thunk from 'redux-thunk';
import rootReducer from './reducers'
const initialState = {};
const middleware = [thunk];
const enhancer = composeWithDevTools(applyMiddleware(...middleware));
const store = createStore(
rootReducer,
initialState,
enhancer
);
export default store;
The index.js file is below
import App from './components/App'
import ReactDOM from 'react-dom';
import React from 'react';
ReactDOM.render(<App />, document.getElementById("app"));
They action types file types.js using django rest_framework to create the data.
export const GET_WORDS = "GET_WORDS";
The action file words.js
import { GET_WORDS } from "./types";
import axios from 'axios';
export const getWords = () => dispatch => {
axios.get('/api/words/')
.then(res => {
dispatch({
type: GET_WORDS,
payload: res.data
});
}).catch(err => console.log(err));
}
combined reducer file
import { combineReducers } from "redux";
import words from './words';
export default combineReducers({
words
});
The reducer file word.js
import { GET_WORDS } from '../actions/types';[enter image description here][1]
const initialState = {
words: []
}
export default function (state = initialState, action) {
switch (action.type) {
case GET_WORDS:
return {
...state,
words: action.payload
}
default:
return state;
}
}
The Component in which the words list will be called: Words.js
import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { getWords } from "../../../actions/words";
export class Words extends Component {
static propTypes = {
words: PropTypes.array.isRequired,
getWords: PropTypes.func.isRequired
};
componentDidMount() {
this.props.getWords();
}
render() {
return (
<Fragment>
Hi
</Fragment>
)
}
}
const mapStateToProps = state => ({
words: state.words.words
});
export default connect(mapStateToProps, { getWords })(Words);
And finally the App component
import React, { Component, Fragment } from 'react';
import Footer from './Layout/Footer/Footer';
import Header from './Layout/Header/Header';
import WordsDashboard from './Content/Words/WordsDashboard';
import { store } from '../store';
import { Provider } from "react-redux";
import { Words } from './Content/Words/Words';
export class App extends Component {
render() {
return (
<Provider store={store}>
<Fragment>
<Header />
React Buddy
<Words />
<Footer />
</Fragment>
</Provider>
)
}
}
export default App;
Your initialState has only words prop, so when mapping it to props you have one extra words level. Try changing it to:
const mapStateToProps = state => ({
words: state.words
});
Also you need to use mapDispatchToProps for getWords, since in your current code you're missing dispatch wrapper:
const mapDispatchToProps = dispatch => ({
getWords: () => dispatch(getWords())
})
export default connect(mapStateToProps, mapDispatchToProps)(Words);
It is my first application using react context with hooks instead of react-redux and would like to get help of the structure of the application.
(I'm NOT using react-redux or redux-saga libraries.)
Context
const AppContext = createContext({
client,
user,
});
One of actions example
export const userActions = (state, dispatch) => {
function getUsers() {
dispatch({ type: types.GET_USERS });
axios
.get("api address")
.then(function(response) {
dispatch({ type: types.GOT_USERS, payload: response.data });
})
.catch(function(error) {
// handle error
});
}
return {
getUsers,
};
};
Reducer (index.js): I used combineReducer function code from the redux library
const AppReducer = combineReducers({
client: clientReducer,
user: userReducer,
});
Root.js
import React, { useContext, useReducer } from "react";
import AppContext from "./context";
import AppReducer from "./reducers";
import { clientActions } from "./actions/clientActions";
import { userActions } from "./actions/userActions";
import App from "./App";
const Root = () => {
const initialState = useContext(AppContext);
const [state, dispatch] = useReducer(AppReducer, initialState);
const clientDispatch = clientActions(state, dispatch);
const userDispatch = userActions(state, dispatch);
return (
<AppContext.Provider
value={{
clientState: state.client,
userState: state.user,
clientDispatch,
userDispatch,
}}
>
<App />
</AppContext.Provider>
);
};
export default Root;
So, whenever the component wants to access the context store or dispatch an action, this is how I do from the component :
import React, { useContext } from "react";
import ListMenu from "../common/ListMenu";
import List from "./List";
import AppContext from "../../context";
import Frame from "../common/Frame";
const Example = props => {
const { match, history } = props;
const { userState, userDispatch } = useContext(AppContext);
// Push to user detail route /user/userId
const selectUserList = userId => {
history.push(`/user/${userId}`);
userDispatch.clearTabValue(true);
};
return (
<Frame>
<ListMenu
dataList={userState.users}
selectDataList={selectUserList}
/>
<List />
</Frame>
);
};
export default Example;
The problem I faced now is that whenever I dispatch an action or try to access to the context store, the all components are re-rendered since the context provider is wrapping entire app.
I was wondering how to fix this entire re-rendering issue (if it is possible to still use my action/reducer folder structure).
Also, I'm fetching data from the action, but I would like to separate this from the action file as well like how we do on redux-saga structure. I was wondering if anybody know how to separate this without using redux/redux-saga.
Thanks and please let me know if you need any code/file to check.
I once had this re-rendering issue and I found this info on the official website:
https://reactjs.org/docs/context.html#caveats
May it will help you too
This effect (updating components on context update) is described in official documentation.
A component calling useContext will always re-render when the context value changes. If re-rendering the component is expensive, you can optimize it by using memoization
Possible solutions to this also described
I see universal solution is to useMemo
For example
const Example = props => {
const { match, history } = props;
const { userState, userDispatch } = useContext(AppContext);
// Push to user detail route /user/userId
const selectUserList = userId => {
history.push(`/user/${userId}`);
userDispatch.clearTabValue(true);
};
const users = userState.users;
return useMemo(() => {
return <Frame>
<ListMenu
dataList={users}
selectDataList={selectUserList}
/>
<List />
</Frame>
}, [users, selectUserList]); // Here are variables that component depends on
};
I also may recommend you to completly switch to Redux. You're almost there with using combineReducers and dispatch. React-redux now exposes useDispatch and useSelector hooks, so you can make your code very close to what you're doing now (replace useContext with useSelector and useReducer with useDispatch. It will require minor changes to arguments)
I am integrating Redux into my React Native app.
I've been having trouble passing the state down into my components, and realised its because the initial state begins with a key of '0', e.g.
{
'0': {
key: 'value'
}
}
I.e. it seems to be an array.
So that if I have in my connect export:
export default connect(state => ({
key: state.key,
reduxState: state
}),
(dispatch) => ({
actions: bindActionCreators(MyActions, dispatch)
})
)(MyReduxApp);
key is always undefined, however, reduxState gets passed down successfully. (Of course it is not advised to send the whole state down)
The root component:
import React, { Component } from 'react-native';
import { createStore, applyMiddleware, combineReducers } from 'redux';
import { Provider } from 'react-redux';
import DataReducer from '../Reducers/Data';
import MyRedApp from './JustTunerApp';
const createStoreWithMiddleware = applyMiddleware(thunk)(createStore);
const reducer = combineReducers([DataReducer]); // ready for more reducers..
// const store = createStoreWithMiddleware(reducer);
const store = createStore(reducer);
export default class App extends Component {
render() {
console.log ("store.getState:")
console.log (store.getState())
return (
<Provider store={store}>
<MyReduxApp />
</Provider>
);
}
}
My reducer looks like:
const initialState = {
key : "value"
};
export default function key(state = initialState, action = {}) {
switch (action.type) {
// ...
default:
return state;
}
}
Is the store returned as an array? How should I pass it into the connect method?
If I pass it in as key: state[0].key then it works, but this doesn't seem right according to all the examples I've seen..
I should have posted the root component earlier... that held the clue.
const reducer = combineReducers([DataReducer])
should have been
const reducer = combineReducers({DataReducer})
the lesson here is don't rush and look at the docs more closely!