One of my redux state part (reducer) is filters reducer, used to store filter settings per page. It's key-object structure, so it looks like this:
{
dashboard: {
minDate: '2017-01-01',
maxDate: '2019-01-01',
//... other filters
},
otherPageKey: {
//... other filters
}
}
My dashboard page is big, but it contains select which value is read from reducer: filters.dashboard.minDate. Code that is responsible for connection:
function mapStateToProps({
filters
}) {
return {
filters: filters.dashboard
};
}
Now - whenever we select new date from that select component, whole filters tree is changed, so the whole dashboard component is being re-renderd.
How can I solve this problem? Expected result is, that only select component, whose property is changed by user, should be re-rendered.
More code would be helpful, but this is usually caused by the state or props being detected by a parent component (in this case, dashboard).
This can be caused by a few things.
You're mapping to parent component rather than the specific child one. This means the parent component is detecting the change and therefore re-rendering. Is your mapStateToProps in your dashboard component as opposed to just in your select component?
Improper use of connect or mapDispatchToProps. Check out this post for more information.
Again, more code would be helpful but I hope this helps.
Related
for a project I am working on I am running into a problem with the nextjs Router.I have a component that has an input field which the user should be able to input their searchterm in. There is a different component which should be able to get this searchterm and perform a search.
Because the two components aren't connected I would like to set the queryParameters in the router in the Input component, and then execute a function in the search component when the searchTerm is changed.
The problem lies in the following: The searchComponent receives the nextJS router as props and will only execute my useEffect function when those props are changed (and react knows they are changed), on top of that I need to stay on the same page when updating the query parameters, but the route of this page is dynamic. For example: the user can add this combination of components on /search but also on /lookforitem.
I have tried setting the queryParameters in the following way in the Input component:
function setQueryParams() {
router.query = {
...router.query,
searchTerm: input.current,
};
}
In combination with the following code in the Search component:
useEffect(() => {
console.log('Router has changed');
}, [router]);
The problem is that this useEffect doesnt get called untill the search component is rendered again (I have created a button that logs the router to the console, and it shows the updated router), which I assume is because React hasn't realised that the Router props have changed.
I have also tried setting the query parameters via a router.push in the following way:
function setQueryParams() {
router.push(
{
pathname: router.route,
query: {
...router.query,
searchTerm: input.current,
},
},
undefined,
{ shallow: true }
);
}
However this comes with its own set of problems. First of all it causes a refresh of the page, which I don't want. On top of that it changes the url to for example: /search?searchTerm=Hello which means that if I enter a different input and submit it will stack making the next url for example: &searchterm=hello?searchterm=goodbye.
I want a way to update the query parameters without refreshing the page, but while also notifying the other components that use the router that the query parameters have updated. All of the searching that I've done seems to be specific to either routing to a different page or routing to a predefined page.
Any help would be greatly appreciated.
I'm writing a simple calendar application that uses a common layout to wrap different views of events (month view shows a larger calendar with all the days of the month and events for each day, week view just shows a vertical list of events for that week, etc.). The common layout includes a calendar picker control for selecting the date, and then a list of event categories that can be checked or unchecked to show events relating to sports, entertainment, etc.
When the layout mounts, I'm calling an async Redux action creator to get the list of event categories from the database. When those are retrieved, they're saved in a Redux store with a property of Selected set to true, since they're all selected at initial load.
async componentWillMount() {
await this.props.getEventTypes();
}
When the month view, which is a child of the layout view, mounts, it's grabbing all the events for the given month. Part of the selection process of getting those events is sending the list of selected event categories to the backend so it only gets events from the selected categories.
async componentWillMount() {
await this.props.getWeeks();
}
The problem is, the selected categories list is always empty when the month view goes to grab the events for the month. So it's not going to select anything since no categories are selected.
It seems the only way this can be happening is if the child component is mounting first, or if the parent component is taking so long to get the event categories that the getWeeks process finishes first (this is unlikely as the process to grab the weeks and days and their events is much more involved than just selecting the event category list).
So, how can I make sure the parent component grabs the event categories from the database and puts them in the Redux store before the child component selects its events?
I know one way, probably the best way, to do this would be to have the list of event categories render into the page on the server side, so it's just already present at initial load. I'll probably end up doing it that way, but I'd also like to know how to do it all through client-side actions, in case I need to do it that way for some reason in the future.
You can try like this
Set isDataLoaded when data is available.
Use ternary operator for conditional rendering.
In you render
return(
<>
....
{ isDataLoaded ? <ChildComponent /> : null }
....other sutff
</>
);
Use can also use the && operator
return(
<>
....
{ isDataLoaded && <ChildComponent /> }
....other sutff
</>
);
You can integrate componentDidUpdate() and use it to render your child-components in a somewhat synchronous flow.
Let's say the structure of your Parent Component should look something like the following:
Parent
class Parent extends React.Component{
state = {
renderChildren: false
}
async componentDidMount() {
await this.props.getEventTypes();
}
componentDidUpdate(prevProps){
if(this.props.yourUpdatedReducer !== prevProps.yourUpdatedReducer){
this.setState({
renderChildren: true
})
}
}
render(){
const { renderChildren } = this.state
return(
{ renderChildren ? <Child/> : "Loading" }
)
}
}
You want a key in your state that determines whether you should
render the Child component.
In componentDidMount(), you call the action-creator function, when
it completes, you get updated props.
With updated props, you trigger componentDidUpdate(), where you
check the values in your reducer. If the values are
different that means you got the updated data from your database, so
everything has loaded.
Great, so now you want to mount your Child component, so you
update-state, setting renderChildren to true, thus re-rendering the
Parent component. Now Child gets rendered and should behave as expected.
It must be pretty regular issue.
I'm passing props down to the children and I'm using it there to request to the endpoint. More detailed: I'm clicking on the list item, I'm checking which item was clicked, I'm passing it to the child component and there basing on prop I passed I'd like to request certain data. All works fine and I'm getting what I need, but only for the first time, ie. when refreshing page incoming props are gone and I cannot construct proper URL where as a query I'd like to use the prop value. Is there a way to preserve the prop so when the page will be refresh it will preserve last prop.
Thank you!
(You might want to take a look at: https://github.com/rt2zz/redux-persist, it is one of my favorites)
Just like a normal web application if the user reloads the page you're going to have your code reloaded. The solution is you need to store the critical data somewhere other than the React state if you want it to survive.
Here's a "template" in pseudo code. I just used a "LocalStorage" class that doesn't exist. You could pick whatever method you wanted.
class Persist extends React.Component {
constuctor(props) {
this.state = {
criticalData = null
}
}
componentDidMount() {
//pseudo code
let criticalData = LocalStorage.get('criticalData')
this.setState({
criticalData: criticalData
})
}
_handleCriticalUpdate(update) {
const merge = {
...LocalStorage.get('criticalData')
...update
}
LocalStorage.put('criticalData', merge)
this.setState({
criticalData: merge
})
}
render() {
<div>
...
<button
onClick={e => {
let update = ...my business logic
this._handleCriticalUpdate(update) //instead of set state
}}
>
....
</div>
}
}
By offloading your critical data to a cookie or the local storage you are injecting persistence into the lifecycle of the component. This means when a user refreshes the page you keep your state.
I hope that helps!
When I am asking this question, lots of doubts are coming into my mind. well, first I will give my problem description.
I have component X. and it contains checkboxes and a search box.
while something typed (call it search_query) in search box,
X needed to update the checkboxes which matches the search_query. [note that I got all the values of checkboxes by some api call. and it is done when component created. ]
First doubts I came to my mind is that
store (search_query) and (values of checkboxes) in component state
if the values are more searching takes more time.
is it possible to change the values of props inside the component
or is there any other way to do it ??
Since no code is shared. Assuming you are using plain React ( no Redux, middlewares are used ).
Answering your question:
[1] Is it possible to change the values of props inside the component?
Changing props values inside the component is a bad approach.
"All React components must act like pure functions with respect to their props."
[ref: https://facebook.github.io/react/docs/components-and-props.html#props-are-read-only]
Also, the view doesn't get the update if props values changed within the component.
[2] or is there any other way to do it.
yes ( without mutation inside the component )
using "state" property to hold values & setState to update values.
[3] How to store the information in react component?
Let's rename component X as FilterData,
searchbox ( SearchBox ) & checkboxes (SelectionBox) are two individual components.
// Define state to FilterData
class FilterData extends React.Component {
constructor() {
super()
this.state = {
term: '',
searchResults: []
}
}
....
}
// pass the state values to child components as props
class FilterData extends React.Component {
....
render() {
return (
<div>
<SearchBox term={this.state.term} />
<SelectionBox options={this.state.searchResults} />
</div>
)
}
}
In React App,
data flows top down (unidirectional) and there should be a single source of truth.
SearchBox & SelectionBox are two individual (sibling) components,
SearchBox's state has terms ( which has the search string )
When the user enters input SearchBox will update its state and possible to detect change event and fire ajax and get the response.
How SelectionBox can detect search that had happened, how it can get data.
This is why the state is moved to common ancestor FilterData.
[Ref: https://facebook.github.io/react/docs/lifting-state-up.html]
[Ref: https://facebook.github.io/react/docs/state-and-lifecycle.html#the-data-flows-down]
Code Sample -----------------------------------------------------
Selected values are not saved:
https://codepen.io/sudhnk/pen/NgWgqe?editors=1010
Selected values are saved:
https://codepen.io/sudhnk/pen/JJjyZw?editors=1010
Background
I am building an app with the following details
react
react-router
redux
it is universal javascript
node js
Problem
When routing with the Link tag from component to component it works perfectly. It calls the data that the component requires and renders the page. But when I click on a Link that uses the same component as the current one all I see is the url change.
Attempts
Things I have tried to get this to work.
Attempt 1
So far I have tried the steps in this question but the solution wont work for me. This was the code I implemented
componentWillReceiveProps(nextProps) {
if (nextProps.article.get('id') !== this.props.article.get('id')) {
console.log('i got trigggerd YESSSSSSSSSSSSSSS');
}
}
But the variable nextProps is always the same as the current props.
Attempt 2
I decided to call the same code I use in componentWillMount but that didn't work either.
componentWillMount() {
let { category, slug } = this.props.params;
this.props.loadArticleState({ category, slug });
}
It just creates an infinite loop when I put this into componentWillReceiveProps.
Conclusion
I belief the problem is clicking the link never calls the data associated with it. Since the data is loaded with
static fetchData({ store, params }) {
let { category, slug } = params;
return store.dispatch(loadArticleState({ category, slug }));
}
Any help is appreciated.
Solution I Used
I created a function to test if the previous data is the same as the changed data.
compareParams(prevProps, props) {
if (!prevProps || typeof prevProps.params !== typeof props.params) {
return false;
}
return Object.is(props.params, prevProps.params);
}
So this tests
are there any previous props?
and then if the props are equal to the previous props?
then return false if there are if this is the case
if not then we see compare props and previous props parameters
In ComponentDidUpdate
In the compoonentDidUpdate we use this function to determine if the data should be updated
componentDidUpdate(prevProps) {
if (this.compareParams(prevProps, this.props)) {
return;
}
this.props[this.constructor.reducerName](this.props.params);
}
Conclusion
This code updates the body of a page that uses the same react component if it receives new data.
maybe you can try use onChange event on Route component, check Route API and then signal to child component that refresh is needed...