I have a component that dispatches action to the reducer using useDispatch hook inside useEffect. I only wants it to happen once on first component render. Then when I try to set the results from the store using useSelector hook I am getting undefined. Here is my code:
const SingleHotel = props => {
//GETTING HOTEL ID FROM REACT-ROUTER-DOM PROPS
const {
match: {
params: { id }
}
} = props
//INITIALIZE USE DISPATCH REDUX HOOK.
const dispatch = useDispatch()
//ACCESSING HOTEL DETAILS STATE FROM THE REDUX STORE.
const hotelDetails = useSelector(state => state.SingleHotel)
//DISPATCHING HOTEL DETAILS TO THE REDUCER.
useEffect(() => {
dispatch(fetchSingleHotel(id))
}, [])
return <div>Single hotel page {console.log(hotelDetails)}</div>
}
I am console logging state from the store in the JSX but I get 'undefined'.
However, when I check the results in the Redux dev tools it appears correctly.
Here is how it looks:
Related
I have a state named chats in which i am storing all messages from firebase and i'm trying to render it in app. but it does not render
here is my component's state:
const [chats, setChats] = useState([]);
I am bringing messages in UseEffect hook to get it on running of app
useEffect(() => {
let merged_uid = uid_merger(current_user.id, chat_user.uid);
database()
.ref('/')
.child(`chats/${merged_uid}`)
.on('child_added', (msgs) => {
console.log(msgs);
chats.push(msgs.val());
setChats(chats);
});
}, []);
but id does not render.
since chats is a state you can't mutate it using chats.push(msgs.val()); instead what you need to do is to replace
chats.push(msgs.val());
setChats(chats);
with
setChats([...chats, msgs.val()])
I have started learning Redux recently, and something is bugging me.
import React, { useEffect } from "react";
import { connect, useDispatch } from "react-redux";
import Modal from "../Modal/Modal";
import history from "../../utils/history";
import { fetchPost } from "../../actions";
const PostDelete = ({ match, post }) => {
const postId = match.params?.id;
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchPost(postId));
}, [dispatch]);
return (
<Modal
/>
);
};
const mapStateToProps = (state, { match }) => {
console.log("MSTP", state.posts[match.params?.id]) // <== CONSOLED TWICE !!
return { post: state.posts[match.params?.id] };
};
export default connect(mapStateToProps, {})(PostDelete);
When I navigate to this using react-router, as per my understanding:
MSTP should be called first(which fetches the post from the store)
Then useEffect() fetches the post(just in case user directly opens this page)
It dispatches the action which changes the state
This re-renders the MSTP again
Is there a way to get around this? Is this a bad approach or am I missing something here?
Explanation
First of all I'd like to say your understanding of what's happening is correct. From the official react-redux documentation it describes that mapStateToProps is called every time the store is updated.
This is ok if you have a fairly simple mapStateToProps object to compute, but can cause performance degradations if you're doing something more intensive. For intensive cases I'd recommend using a memoized selector, which will just return the previously calculated mapStateToProps value, without doing any new computations, if no relevant changes were made to the store. A good library for achieving this is reselect.
Even with a memoized selector, your console.log('MSTP') statement will be printed, but the underlying computation will be quicker.
Code Example
Consider the following example.
Component is rendered for the first time
useEffect fetches the post and updates the store at state.posts (relevant to this component)
Some other component updates the redux state, at an irrelevant part to this component, e.g. state.comments
Here's the code and console output BEFORE using a memoized selector
const intensivePostsFormatting = (state) => {
console.log('Formatting Posts');
// do some stuff with state.posts
return formattedPosts;
}
const mapStateToProps = ({ match, state }) => {
console.log('MSTP');
return {
posts: intensivePostsFormatting(state)
}
}
// Console Output:
// MSTP
// Formatting Posts
// MSTP
// Formatting Posts
// MSTP
// Formatting Posts
Here's the code and output AFTER using a memoized selector
import { createSelector } from 'reselect';
const intensivePostsFormatting = (posts) => {
console.log('Formatting Posts');
// do some stuff with posts
return formattedPosts;
}
const postsSelector = createSelector(
state => state.posts,
posts => intensivePostsFormatting(posts)
)
const mapStateToProps = ({match, state }) => {
console.log('MSTP');
return {
posts: postsSelector(state)
}
}
// Console Output:
// MSTP
// Formatting Posts
// MSTP
// Formatting Posts
// MSTP
Note that the difference between the before and after, is that "Formatting Posts" is logged 3 times in the "before" example and 2 times in the "after" example. This is because using a memoized selector allowed us to skip computing the formatted posts when a change to something other than state.posts was made.
I'm building a headless eCommerce site using React/Next and have a [product].js dynamic route which is used to generate all product pages, using getStaticPaths() and getStaticProps() which generates the pages fine.
I'm using useState hook within [product].js to manage a number input (for quantity) and a couple of other things.
The first product page loaded works fine, but when I go to other product pages, they use the same state from the first product.
Is there a way to have the state NOT persist between route changes?
Through some digging, I found that this is an issue with next and is in their backlog. It essentially stems from the fact that the component doesn't have a key. This means switching between routes on the same dynamic route doesn't register correctly and causes the component to use stale state.
A possible solution I found was this:
export async function getStaticProps({params}) {
const props = await getData(params);
// key is needed here
props.key = data.id;
return {
props: props
}
}
This is my implementation which doesn't work for me:
export default function ProductPage(props) {
// this state doesn't reset between dynaic route changes
const [quantity, setQuantity] = useState(1)
return(
...
)
}
export async function getStaticProps({ params }) {
const slug = params.product
const props = await client.query({
query: singleProductQuery,
variables: { id: slug }
})
props.key = props.data.product.slug
return {
props: props
}
}
I tried wrapping the contents within another component and adding a key to that, like so:
return(
<OuterComponent key={props.id}>
// components within here, that have their own state, now work
</OuterComponent>
)
Since this new keyed component is only in the return statement and does not encapsulate the state hook, it does not work. This does reset the state however, for any components found within wrapped component.
You can use useEffect hook and useRouter hook at dynamic router to reset the state.
import {useState, useEffect} from 'react'
import {useRouter} from 'next/router'
const ProductPage = (props) => {
const [state, setState] = useState(someState)
const dynamicRoute = useRouter().asPath
useEffect(() => {
setState(resetState) // When the dynamic route change reset the state
}, [dynamicRoute])
//Some other logic
return (
......
)
}
It seems that you've encountered the same issue thread that I've found:
https://github.com/vercel/next.js/issues/9992
It seems from what I've read that to fix your case, all you need to do is change your getStaticProps to return an object with a unique key:
export async function getStaticProps({ params }) {
const slug = params.product
const props = await client.query({
query: singleProductQuery,
variables: { id: slug }
});
return {
props: props,
key: slug
}
}
What you've been doing previously is passing a key to the props object instead of root return object for getStaticProps
You can use useEffect hook to reset state
export default function ProductPage(props) {
// this state doesn't reset between dynaic route changes
const [quantity, setQuantity] = useState(1)
useEffect(() => {
setQuantity(props.quantity) // <-- this props comes from getStaticProps
}, [props]) // <--- useEffect will keep tracking changing props
return(
...
)
}
So when your props changes - your state updates.
My folder structure:
|--App
|--Components
|--PageA.js
|--PageB.js
|--PageC.js
|--common-effects
|--useFetching.js
I am refactoring my code to fetch data from API, using react hooks.
I want to dispatch an action from useEffect in useFetching.js that is intercepted by saga middleware. The action should be dispatched only when the components(PageA, PageB, PageC) mount.
I am using redux, react-redux and redux-saga.
PageA.js:
function(props) {
useFetching(actionParams)
//....//
}
Similar code for PageB and PageC components.
I have abstracted the reusable code to fetch data in useFetching Custom hook.
useFetching.js
const useFetching = actionArgs => {
useEffect( () => {
store.dispatch(action(actionArgs)); // does not work
})
}
I don't know how to access redux dispatch in useFetching. I tried it with useReducer effect, but the sagas missed the action.
Version using react-redux hooks:
You can even cut out the connect function completely by using useDispatch from react-redux:
export default function MyComponent() {
useFetching(fetchSomething);
return <div>Doing some fetching!</div>
}
with your custom hook
import { useDispatch } from 'react-redux';
const useFetching = (someFetchActionCreator) => {
const dispatch = useDispatch();
useEffect(() => {
dispatch(someFetchActionCreator());
}, [])
}
Edit: removed dispatch from custom hook as suggested by #yonga-springfield
Note: React guarantees that dispatch function identity is stable and won’t change on re-renders. This is why it’s safe to omit from the useEffect or useCallback dependency list.
You would need to pass either bound action creators or a reference to dispatch to your hook. These would come from a connected component, same as you would normally use React-Redux:
function MyComponent(props) {
useFetching(props.fetchSomething);
return <div>Doing some fetching!</div>
}
const mapDispatch = {
fetchSomething
};
export default connect(null, mapDispatch)(MyComponent);
The hook should then call the bound action creator in the effect, which will dispatch the action accordingly.
Also, note that your current hook will re-run the effect every time the component is re-rendered, rather than just the first time. You'd need to modify the hook like this:
const useFetching = someFetchActionCreator => {
useEffect( () => {
someFetchActionCreator();
}, [])
}
This is just to bring some optimization to #Alex Hans' answer.
As per the documentation here. A custom Hook is a JavaScript function whose name starts with ”use” and that may call other Hooks.
With this in mind, we need not send a reference to the dispatch function to the useFetching hook as a parameter but rather, simply not send it and rather simply use it from within the useFetching hook with the appropriate imports.
Here's an excerpt of what I mean.
import { useDispatch } from 'react-redux';
const useFetching = (someFetchActionCreator) => {
const dispatch = useDispatch()
useEffect(() => {
dispatch(someFetchActionCreator());
}, [])
}
I can't ascertain this example will fit without errors in your codebase in your case but just trying to explain the idea/concept behind this post.
Hope this helps any future comer.
Alex Hans right decision with dispatch, but to eliminate request loops to api you can specify the dependence on dispatch ( I used Redux Toolkit )
import React, { useEffect } from 'react'
import { useDispatch } from 'react-redux'
import axios from 'axios'
import { getItemsStart, getItemsSuccess, getItemsFailure } from '../features/itemsSlice'
const fetchItems = () => async dispatch => {
try {
dispatch(getItemsStart());
const { data } = await axios.get('url/api')
dispatch(getItemsSuccess(data))
} catch (error) {
dispatch(getItemsFailure(error))
}
}
const PageA = () => {
const dispatch = useDispatch()
const { items } = useSelector(state => state.dataSlice)
useEffect(() => {
dispatch(fetchItems())
}, [dispatch])
return (
<ul>
{items.map(item => <li>{item.name}</li>}
</ul>
)
}
export default PageA
it is important to passed dependency parameter of dispatch in the useEffect(() => {...}, [dispatch])
useEffect(() => {
fetchData();
}, []);
async function fetchData() {
try {
await Auth.currentSession();
userHasAuthenticated(true);
} catch (e) {
if (e !== "No current user") {
alert(e);
}
}
dispatch(authentication({ type: "SET_AUTHING", payload: false }));
}
I am new to ReactJS. I am trying to use ReactJS and redux together.
Can anybody explain how to connect the two?
Also, in redux reducer, we usually set the intialState to some value. How can I set the value which is already present in react component's state to the initialState of reducer?
Below is initialization done in my reducer,
const initialState = {
pricing:{
total:0
},
appliedPromo:false
}
Below is my state in react component,
state = {
pricing: {},
itemDetails: {},
error: false,
seeDetails:false,
showPromoCode:false,
promocodeApplied:false,
user_discount:""
}
I will update the state using axios,
componentDidMount() {
axios
.get("https://purchsum-fb152.firebaseio.com/data.json")
.then(response => {
this.setState({ pricing: response.data.Pricing, itemDetails: response.data.itemDetails });
})
.catch(error => {
this.setState({ error: true });
});
}
then I will connect state to props,
const mapStateToProps = (state) => {
return{
total: state.pricing.total,
promoApplied: state.appliedPromo
}
}
const mapDispatchToProp = dispatch =>{
return{
onDiscount:()=>dispatch({type:"DISCOUNT",value:10,percentage:100})
}
}
export default connect(mapStateToProps,mapDispatchToProp)(PurchaseOrder);
The application works only if i set initialState as following,
const initialState = {
pricing:{
total:108.03
},
appliedPromo:false
}
I don't want to set it (no hard code). Instead I want reducer to take state value which is updated in the component.
I don't want to set it (no hard code). Instead I want reducer to take
state value which is updated in the component.
That's not the redux pattern unfortunately. You must set the intial state for the reducer to load up - I would not suggest that this can or should be done dynamically.
However, can you not create an action which will set the state to what you want when your Component mounts?