Should we render react component at multiple places? - reactjs

So, i have component called Balance component which i want to display in navbar and on a page (summary page) component as well.
But i want to prevent it from making an api request twice.
The code of balanceSlice:
import {createSlice, createAsyncThunk} from '#reduxjs/toolkit';
export const updateBalance = createAsyncThunk(
'balance/fetchBalance',
async () => {
const response = await fetchBalance();
return response.data;
}
);
const initialState = {
value: 0
};
export const balanceSlice = createSlice({
name: 'balance',
initialState,
reducers: {
},
extraReducers: () => {...}
});
export const selectBalance = (state) => state.balance.value;
export default balanceSlice.reducer;
Here is the code of Balance
import {useState, useEffect} from 'react';
import {useSelector, useDispatch} from 'react-redux';
import {updateBalance, selectBalance} from './balanceSlice';
function Balance() {
const balance = useSelector(selectBalance);
const dispatch = useDispatch();
useEffect(() => {
dispatch(updateBalance());
console.log('component ran')
}, [dispatch]);
return (
<>
{balance}
</>
);
}
export default Balance;
As i want to reuse this component at multiple places, so i haven't added any styling in this component. The styling is being done in summary page and the navbar. The updateBalance is requesting new balance from the api. I am using redux toolkit and I am trying to follow single responsibility principle and making component small, reusable and testable.
Is it recommended to render balance component at multiple places?
Navbar code:
import Balance from './../../features/balance/Balance';
function TopNavbar() {
return (
<Navbar>
<span className={styles.navbar__text__span}><Balance /></span>
</Navbar>
);
}
Similarly, i am importing Balance component in summaryPage component and rendering it. Is it a recommended approach or should i simply import selectBalance from balanceSlice and display it in navbar?

You can render Balance components wherever you need.
Balance component would look like this.
function Balance() {
const balance = useSelector(selectBalance);
const dispatch = useDispatch();
useEffect(() => {
if (balance.status === 'ready') {
dispatch(updateBalance());
}
console.log('component ran')
}, [dispatch]);
return (
<>
{balance.value ?? 0}
</>
);
}
In updateBalance action, you should set the balance status to 'fetching' and call the api. After api call is finished, you can set the balance status to 'fetched' and set the balance value to the fetched value. That's all I guess.

Related

Can you use setReduxObject and selectReduxObject in the same .jsx page?

I am learning Redux and I have deviated from the instructor's code. I am trying to convert my code from context & state into Redux.
Is it advisable to use setReduxObject (setCategoriesMap in my code) and selectReduxObject (selectCategoriesMap in my code) in the same .jsx page? Are there any concerns around this?
Thanks!
My code:
import { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { getCategoriesAndDocuments } from "../../utils/firebase/firebase.utils";
import { setCategoriesMap } from "../../store/categories/categories.action";
import { selectCategoriesMap } from "../../store/categories/categories.selector";
import Category from "../../components/category/category.component";
const Shop = () => {
const dispatch = useDispatch();
useEffect(() => {
const getCategoriesMap = async () => {
const categories = await getCategoriesAndDocuments();
dispatch(setCategoriesMap(categories));
};
getCategoriesMap();
}, []);
const categoriesMap = useSelector(selectCategoriesMap);
return (
<div>
{Object.keys(categoriesMap).map((key) => {
const products = categoriesMap[key];
return <Category key={key} title={key} products={products} />;
})}
</div>
);
};
export default Shop;
This is just the default approach, nothing to be concerned about.
As soon as you're using getCategoriesAndDocuments the same way in another component though, it's better to move this to an async action creator.
Could even do it for this component already to improve separation of concerns. The component does not necessarily need to be involved with firebase, its job is display logic. Wether the data comes from firebase or localStorage or some graphQL server should not matter.

React Context not updating value to pass to another page

I am creating an ecommerce app with Nextjs and want to share data between pages. I know that we can't use props to pass data between the pages and so was looking into react context api. This is my first time using react context api. I've researched and found that you should add the Provider in the _app.js page in nextjs.
But this shares the data among all the pages. Plus my data is being retrieved by getStaticProps in the slug page of the app. I want to get this data into the checkout page of my app.
This is the context I have created:
import { createContext, useState, useContext } from 'react';
const productContext = createContext({} as any);
export const ProductProvider = ({ children }) => {
const [productData, setProductData] = useState('');
return <productontext.Provider value={{ productData, setProductData }}>{children}</productContext.Provider>;
};
export const useProduct = () => useContext(productContext);
_app.js
import { ReportProvider } from '../contexts/ReportContext';
export default function CustomApp({ Component, pageProps }) {
return (
<ReportProvider>
<Component {...pageProps} />
</ReportProvider>
);
}
I import this into the slug page and try to update the state from here
// [slug].js
import client from '../../client'
import {useProduct} from './productContext';
const Post = (props) => {
const {setProductData} = useProduct();
const { title = 'Missing title', name = 'Missing name' , price} = props
setProductData(title);
return (
<article>
<h1>{title}</h1>
<span>By {name}</span>
<button>
Buy Now
</button>
</article>
)
}
Post.getInitialProps = async function(context) {
const { slug = "" } = context.query
return await client.fetch(`
*[_type == "post" && slug.current == $slug][0]{title, "name": author->name, price}
`, { slug })
}
export default Post
However this productData is not accessible from another page and the react context state is not getting updated.
Any idea why this could be happening?
Once you've updated your context value. Please make sure you are using next/link to navigate between pages. Here is details about next/link

How to fetch Data on load component using React-Redux and Axios?

I have a need to fetch data from an API on component load, am using axios to fetch data, I need to save the response to the state and get back when the component load.
But i could do as am new to this.
My codes as below.
Sales.js : (This is where I fetch My components)
function SalesDesk() {
return (
<div>
<FoodScreen />
</div>
)}
export default SalesDesk;
FoodScreen.js (This is where i need to list my results to a variable, to map it later)
function FoodScreen() {
return(
<div className="sdFoodScreenMain">
{console.log(items)} // The results should be displayed here
</div>
)}
export default FoodScreen;
API.js (Here is where where i use my axios Router)
const API_URL = `https://jsonplaceholder.typicode.com/`; //Mock api for test purposes
export const GetAllItems = () => {
return (dispatch) => {
axios.get(API_URL)
.then(response => {
dispatch(allItemsList(response.data));
})
}
};
ItemsReducer.js (The reducer Logic)
const ItemsReducer =(state:Array = null, action) =>{
if (action.type === 'ALL_ITEMS') {
return GetAllItems ;
} else {
return state= null;
}
};
export default ItemsReducer
SalesAction.js (Action list)
export const allItemsList = () => {
return {
type: 'ALL_ITEMS'
};
};
All I need to do is fetch the the data from the API and display it in the console, when the component renders.so that I can display it in a map of div boxes for future purposes. Am new to both react and Redux, so ignore if any logic or implementation issues.
At first Router.js is a bad name(api.js etc), You should connect Sales.js to redux, using { connect } from 'react-redux'. See there https://redux.js.org/basics/usage-with-react and call action to fetch data in Sales.js
All I had to add an useDispatch on the component render, so it could fetch the data to the component on load.
import Reactfrom 'react'
import {useDispatch} from "react-redux";
import {GetAllItems} from 'API' //File containing the axios function
export function SalesDesk() {
const dispatch = useDispatch();
dispatch(GetAllItems())
return (
<div>
<FoodScreen />
</div>
)}
This helped me to fetch add add to state on component load.

I am using React Context and would like to get confirmed my structure

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)

React redux create an action that takes the state from other part of application

I have a react/redux application and use redux-thunk.
I have a button in one part of application that when clicked triggers an action that takes the state of some completely different part of application and sends it to backend. When backend response arrives it should modify some application state. How can I do that?
Check the below example:
Part of app with button:
// MyModalContainer.js
import {connect} from 'react-redux';
import {MyModal} from './MyModal';
const mapStateToProps = (state) => ({
});
const mapDispatchToProps = (dispatch) => ({
orderButtonHandler: () => {...to do...}
});
const MyModalContainer = connect(mapStateToProps, mapDispatchToProps)(MyModal);
export {MyModalContainer};
///////////////////////////////////
// MyModal.js
import React from 'react';
import Button from '#material-ui/core/Button';
class MyModal extends React.Component {
render() {
return (
<div>
<Something>....</Something>
<Something2>....</Something2>
<Button onClick={this.props.orderButtonHandler} color="primary">
Order
</Button>
</div>
);
}
}
export {MyModal};
Data to be sent to backend on button click is stored in redux under:
state.cartData = {
data1: ....,
data2: ....,
data3: ....
}
and is not rendered in MyModal.
One option is that I can send state.cartData to MyModal through MyModalContainer, and then the button will send it to the orderButtonHandler. But then MyModal will redraw any time state.cartData changes even if it doesn't draw anything from state.cartData.
I found the solution that satisfies me, but if you have better solution please post, or comment below this answer.
// MyModalContainer.js
import {connect} from 'react-redux';
import {MyModal} from './MyModal';
import { makeOrder } from './modules/cart';
const mapStateToProps = (state) => ({
});
const mapDispatchToProps = (dispatch) => ({
orderButtonHandler: () => {dispatch(makeOrder());}
});
const MyModalContainer = connect(mapStateToProps, mapDispatchToProps)(MyModal);
export {MyModalContainer};
The makeOrder function utilizes redux-thunk with getState:
// modules/cart.js
const makeOrders = () => async (dispatch, getState) => {
const stateOfSomeOtherPartOfApp = getState().cart;
// THE CRITICAL PART IS TO GET THE STATE IN THE ASYNC ACTION CREATOR
// AS I FOUND OUT THAT getState IS PROVIDED BY REDUX THUNK
const orderResponse = await makeAsyncPost(stateOfSomeOtherPartOfApp);
dispatch({type: 'orderResponseReceived', data: orderResponse});
};

Resources