how to auto refresh component when redux state get updated - reactjs

I currently store user name in redux store and show it in topbar after user logged in. (Shown in screenshots). However, it do not re-render automatically when redux state gets updated. I still need to do some actions in topbar then username does appear. Otherwise, it does not appear. So the question, how to force to re-render topbar component from my login component.
Another question: user name will disapprear when refresh page. So is there any option to persist data with redux state? Or it's impossible.
const Login = () => {
...
const handleLogin = async googleData => {
...
//store user name value als application state into redux store
store.dispatch(nameActionCreator(googleData.profileObj.name));
...
}
}
const TopBar = () => {
...
return(
...
<h5>store.getState().userNameRecuder.name</h5>
...
)
}
Before login
After login

The first question
Short answer:
you must call the selector function with useSelector hook. you are calling it directly in your <h5> element. so it's not working.
const TopBar = () => {
const username = useSelector(() => store.getState().userNameRecuder.name)
return(
<h5>{username}</h5>
)
}
Long answer with best practices:
You need to create a selector and use it in your component to re-render your component after the state changes.
In the selector.js
const selectUsername = (state) => state.userNameRecuder.name
export selectUsername;
Now, in your Topbar component, you need to implement the useSelector hook:
import {useSelector} from 'react-redux';
import {selectUsername} from 'store/selectors';
function TopBar () {
const username = useSelector(selectUsername)
return (
<p>username</p>
)
}
Updating the state with proper action (to update username) will cause a re-render in the Topbar component to get the new username from the store.
The second question
There are many great ways to persist data for example storing them on cookies, localStorage or using third-party libraries like redux-persist, and so on...
take a look at this post.

It works based on #novonimo's answer. Even without apart selector.js. Just connect the redux state with the component's state by using useSelector hooks, so the component will automatically re-rendered when the redux state changes.
const TopBar = () => {
const userName = useSelector(() => store.getState().userNameRecuder.name);
return(
...
<h5>userName</h5>
...
)
}

Related

Mixing Redux with useEffect Hook

I read that this is theoretically OK. In my small use case, I'm running into an issue, where mixing those technologies leads to double re-rendering.
First, when redux dispatch is executed and some components use a prop via useSelector. Then, after the functional component is already re-rendered, a useEffect hook is being applied which updates the state of some property. This update re-triggers the render again.
E.g. the below console log prints out twice in my case.
Question: should I remove the useEffect and useState and integrate it into redux' store?
import {useSelector} from "react-redux";
import React from "react";
const Dashboard = (props) => {
const selFilters = useSelector((state) => state.filter.selectedDashboardFilters);
const [columns, setColumns] = React.useState([]);
React.useEffect(() => {
let newColumns = getColumns();
setColumns(newColumns)
}, [selFilters]
)
console.log("RENDER")
return (
<h1>{columns.length}</h1>
)
}
If columns needs to be recomputed whenever selFilters changes, you almost certainly shouldn't be recomputing it within your component. If columns is computed from selFilters, then you likely don't need to store it as state at all. Instead, you could use reselect to create a getColumns() selector that derives the columns from the state whenever the relevant state changes. For example:
const getColumns = createSelector(
state => state.filter.selectedDashboardFilters,
selFilters => {
// Compute `columns` here
// ...
return columns
}
)

why I'm not getting the actual state with redux?

I'm getting the state to populate my form but is not the actual state, apparently it is the initial state.
import React from "react";
import store from "../store";
const Invoice = () => {
console.log(store.getState().login);
return (
<div className="span7">
<h4 className="title">
<span className="text">
<strong>Datos</strong> de facturaciĆ³n
</span>
</h4>
...
However, my redux chrome DevTools is showing the actual state (the right one), why the difference?
Decide on 1 source of truth for your state - if you believe it should live in redux, then select the state from redux and DON'T combine it with a local useState:
import React from "react";
import { useSelector } from "react-redux";
const Invoice = () => {
const userAuth = useSelector(state => state.login);
console.log(userAuth);
}
If you want to log current state (from redux), I think you should use subscription (store.subscribe(...)) and put it into useEffect hook.
Something like this:
useEffect(() => {
const subscription = store.subscribe(() => {
console.log(store.getState().login);
});
return () => { subscription() } // clear out subscription when component unmounts
},[])
You are seeing difference between redux dev tools and your conslole.log because with store.getState() you are getting state at it was when your component was mounted. With subscribe you are getting notified any time some action get dispatched an state get changed.

PureComponent keeps rendering even though no state/prop changes

I'm trying to learn and test React.PureComponent and it keeps rendering even though no state changes for that pure component.
My PureComponent is very simple and it accepts only one Redux Action function via connect hoc
import React from 'react';
import {
Container,
Button
} from 'reactstrap'
import { connect } from 'react-redux'
import { resetWorkouts } from '../actions/workoutApiActions'
class About extends React.PureComponent {
render () {
const { resetWorkouts } = this.props;
console.log('in about render...')
return (
<React.Fragment>
<Container>
<h2>Api Data Reset</h2>
<Button color="danger" onClick={resetWorkouts}>Reset Data</Button>
</Container>
</React.Fragment>
);
}
}
const mapDispatchToProps = dispatch => {
return {
resetWorkouts: () => dispatch(resetWorkouts())
}
}
export default connect(null, mapDispatchToProps)(About);
In the above code, you can see that, there is no state in the component. It only accepts the action function as props from connect. However, whenever I clicks on the Reset Data button, it keeps calling the render method as shown in the screenshot.
In the screenshot, I can see that, global state store has been changed whenever, I click a button. But, that state is not used in my PureComponent and it should be out of the scope and my component should ignore to re-render.
Or Redux Action functions are created every time the global state store has been changed. And passed as a new object to my PureComponent ?
Theoretically, I don't need to write my own shouldComponentUpdate function, right? I'm confused and could you please help me to understand about this behaviour?
My goal is I don't want my PureComponent to render again when a user clicks a button.
Updates:
I have tried like the following according to this article and it's still re-rendering
const mapDispatchToProps = {
resetWorkouts
};
this because react do a shallow comparison between the prevProps and the nextProps,
and you can control that only in the shouldComponentUpdate, react doesn't know that the dispatcher is the same one from the previous render, because you are using return inside the mapDispatchToProps function.
In your component and in your case, while the function will remain the same, you can go with two paths:
path 1:
override the shouldComponentUpdate life cycle hook, to be as the following:
shouldComponentUpdate(){
return false;
}
path 2:
get rid of the return inside mapDispatchToProps and simplify the connect so it be as the following:
`conncect(state => ({}), {
resetWorkouts: resetWorkouts})(YourComponent);`
using one of the above paths should make you good to go
The reason why your component is rendering is because everytime the following function executes:
const mapDispatchToProps = dispatch => {
return {
resetWorkouts: () => dispatch(resetWorkouts())
}
}
your components receives a new instance of a property named resetWorkouts(because you're creating an inline array function). You may look at the ownProps to check if your component already have the resetWorkouts:
const mapDispatchToProps = (dispatch, ownProps) => {
return {
resetWorkouts: ownProps.resetWorkouts || () => dispatch(resetWorkouts())
}
}

React Redux store state change is not working

The below super simple code is not working as expected.
I am using react and injecting/providing props via redux store. what my understanding is store inject props in the React component.
To make this component work why I need both line1 and line 2 ?
import React from 'react';
import './sytles.css';
import { fetchUsers } from "./actions/userAction"
import { connect } from "react-redux"
#connect((store) => {
return {
signup: store.users,
};
})
class Signup extends React.Component {
handleClick(event) {
this.setState({email: event.target.value}) //line 1
this.props.signup.email = event.target.value; // line 2
}
render() {
return (
<input
type="text"
name="email"
value={this.props.signup.email}
onChange=
{ (event) => this.handleClick(event) }/>
);
}
}
export default Signup;
You can't reassign props -- it's read only even aside from Redux. But to change the Redux store, you dispatch an action. Per the Redux documentation of The Three Principles of Redux:
State is read-only
The only way to change the state is to emit an action, an object describing what happened.
This ensures that neither the views nor the network callbacks will ever write directly to the state. Instead, they express an intent to transform the state. Because all changes are centralized and happen one by one in a strict order, there are no subtle race conditions to watch out for. As actions are just plain objects, they can be logged, serialized, stored, and later replayed for debugging or testing purposes.
You are doing it incorrectly, props must never be mutated directly, also you shouldn't keep a state that is directly derivable from props. Since your data signup is present in store, you need an action creator that updates this value in the store
const handleSignup = (email) => {
return {
type: "SIGNUP_EMAIL",
email
}
}
and dispatch it like
handleClick(event) {
dispatch(handleSignup(event.target.value));
}

React Redux - Conditional selector

In my application i have many part of the state that is significative only when the user is logged in.
When the user is logged in and navigate directly to a page, router display the page but have to make async call, so a piece of the state is not yet populated.
Now, suppose i have to show a button on navigation bar that have to take a part of the state, but this state is not populated since the async call finis.
More pratically, i have this selector:
export const getSelectedCustomer = state => state.systems.selectedCustomer;
With this, there are no problem, because selectedCustomer is part of the initialState reducer
The problem is in this selector:
export const getSelectedCustomerName = state => {
return state.systems.entities[getSelectedCustomer(state)].customer;
}
The part of the state entities in initialState is an empty array and will be populated on async call.
So, when the application start and i map (with connect - mapStateToProps) the selector getSelectedCustomerName i get the error because entities is empty and sure, customer field do not exist yet
A simple solution if to take in place some if-else, but i will have to do this in "every" part of the application.
I'm asking if there is a better solution to avoid this...
const NullCustomerEntities = {
customer: "Anonymous"
}
export const getSelectedCustomer = state => state.systems.selectedCustomer;
export const getCustomerEntities = state => {
return state.systems.entities[getSelectedCustomer(state)] || NullCustomerEntities;
}
export const getSelectedCustomerName = state => {
return getCustomerEntities(state).customer;
}
This is a classic example of a Null Object pattern
(hope you don't mind the code being in Ruby).

Resources