Make conditional react component rerender after boolean changes - javascript

I have a react component that conditionally renders JSX according to the user's login state.
<div>
{ boolIsLoggedIn ?
<SomeLoggedInComponent /> : <SomeNotLoggedInComponent /> }
</div>
I think I need to use React.useState() and/or React.useEffect() but I'm not sure exactly how to implement it.
I've tried this:
const [boolIsLoggedIn, setBoolIsLoggedIn] = useState(isLoggedIn())
useEffect(() => {
const checkLogIn = () => {
setBoolIsLoggedIn(isLoggedIn())
}
checkLogIn()
})
Where isLoggedIn() checks whether the user is logged in or not and returns a boolean.
Currently, I can log out and log in and the isLoggedIn() function works, but the component I want to conditionally re-render doesn't do so until I refresh the page.
So I added [isLoggedin()] as the second parameter to useEffect() and now it almost works. When I log in, the boolIsLoggedIn value changes to true and the component re-renders. However, when I log back out boolIsLoggedIn doesn't change until I refresh the page.
Here is my code for the isLoggedIn() function, which is coded in a seperate file and imported:
let boolIsLoggedIn = false
export const setUser = user => {
//handle login
boolIsLoggedIn = true
export const logout => {
//handle logout
boolIsLoggedIn = false
}
export const isLoggedIn = () =>
return boolIsLoggedIn
}

try this
import React, { Component } from "react";
import './App.css';
class App extends Component {
constructor(props) {
super(props);
this.state = {
isLoggedIn: isLoggedIn()
};
}
render() {
let { isLoggedIn } = this.state;
return (
<div className="App">
{(function() {
if (isLoggedIn) {
return <div>Login</div>;
} else {
return <div>with out Login</div>;
}
})()}
</div>
);
}
}
export default App;

You are missing the open [ on defining the state.
I would set the defailt value on false so you won't execute the function twice.
The use effect usually needs a dependencies array as the second parameter but as you only want to run it once (after the component is mounted) just place an empty array as the second parameter.
Another thing is that if the isLoggedIn function is asyncronous you have to wait for it by using await and setting the parent function as async. This would definetly be the problem you have if the function is asyncronous.

Change This:
const boolIsLoggedIn, setBoolIsLoggedIn] = useState(isLoggedIn())
useEffect(() => {
const checkLogIn = () => {
setBoolIsLoggedIn(isLoggedIn())
}
checkLogIn()
})
To this:
const [boolIsLoggedIn, setBoolIsLoggedIn] = useState(isLoggedIn());
useEffect(() => {
(() => {
setBoolIsLoggedIn(isLoggedIn());
})();
}, [isLoggedIn()]);
Good Luck

You need to be able to share react state, and not just values in order to trigger a react re-render. In your example, you would need a call to setBoolIsLoggedIn() whenever value of isLoggedIn() changes in order to change the state and trigger a re-render of the component.
Another way share state between components is through React Context. You would first need to create a Context Provider like this:
const UserContext = React.createContext();
export function UserProvider({ children }) {
const [ user, setUser ] = useState({ loggedIn: false });
return (
<UserContext.Provider value={[ user, setUser ]}>
{ children }
</UserContext.Provider>
)
}
In this case, the UserProvider is the one maintaining the shared state withuser={ loggedIn }. You would then need to wrap your React App component with this provider in order for components to be able to share state.
To set the shared user state you can use the hook useContext(UserContext) and change state through setUser provided by the hook. In the example below, we have a login button component that sets shared user state:
function LoginButton() {
const [user, setUser] = useContext(UserContext);
/** flip login state when button is clicked */
function logIn() {
setUser(state => ({ ...state, loggedIn: !state.loggedIn }) );
}
return (
<button onClick={logIn}>{ user.loggedIn ? "Log Out" : "Log In"}</button>
);
}
And finally, to be able to use the shared user state and trigger a re-render whenever the user state changes, you would again use the hook useContext(UserContext) in a different component to be able to get the state like this:
export default function App() {
const [user,] = useContext(UserContext);
return (
<div className="App">
<h1>Conditional React component</h1>
<h2>Click on the button to login/logout</h2>
<LoginButton></LoginButton>
<div>
{ user.loggedIn ? <LoggedInComponent/> : <LoggedOutComponent/>}
</div>
</div>
);
}
I've provided an example here to show how this all works together.

Related

Throw error when creating react component conditionally

I have a react component called ListChats and another called Chat.
ListChats calls the rendering of a Chat
I need that if an error occurs when rendering the Chat, ListChats knows and comes back with the list of available chats.
A possible error, its capture has not yet been implemented, would be if the user has no name, Chat captures this error and returns to ListChat.
ListChats component:
import React, { Component } from "react";
import Chat from "../Chat";
import { Button } from 'react-bootstrap';
export default class ListChats extends Component {
constructor(props) {
super(props);
this.state = {
chat: <div />
}
this.toSala = this.toSala.bind(this);
}
toSala() {
//this is where I render a Chat component, I need that, if it returns an error, I set this.state.chat to "<div />"
this.setState({chat: <Chat/> });
}
render() {
const { chat} = this.state;
return (
<>
<Button onClick={this.toSala}>abrir chat</Button>
{chat}
</>
)
}
};
Chat component
import React,{ useState, useEffect } from 'react'
const Chat = (props) => {
const [name, setName] = useState('');
const [room, setRoom] = useState('');
const [users, setUsers] = useState([]);
useEffect(() => {
//depending on the value of a variable of mine I have the need or not to throw an error when rendering this component
}, []);
return (
<div>
my chat
</div>
);
};
export default Chat;
In short, I need to throw an error in the Chat component and catch it in ListChats component
you can do this in two ways:
first you can pass props from the parent to the child and communicate between the two components by this.set your variable in the parent component and pass that via props to the child component.in this case your child component is responsible to show whatever comes from the parent.
the second one is using a global state management like Redux,ContextApi or mobX to catch error in the child and save it to an state and then use that state in the parent component or wherever you want to use.
depend on the size of your project you can use either way.
Thanks to the responses and comments I managed to elaborate the following solution:
1- I created a function in ListChat that will receive the error message (msg) by parameter:
setError = (msg) => {
this.setState({error:msg});
}
2- I passed the function as props in rendering the chat:
this.setState({chat: <Chat setError={this.setError}/> });
3- When I need to now pass an error by the Chat for ListChat,
I call in the Chat component:
props.setError("Name is null");

What is the best practices for redirecting users in React applications?

I have seen much more cases related to redirecting users in react applications and every case was just a different approach to the solution. There are some cases, where redirecting has occurred in actions like this`
export const someAction = (values, history) => async dispatch => {
const res = await someAsyncOperation(props);
history.push('/home');
dispatch(someAction);
}
In this example history object (form react-router) is being passed in react component. For me, this approach is not acceptable.
There is also a special Redirect from react-router.
After then I have already searched many articles and couldn't just find anything.
So in your opinion, what's the best practice for redirecting and where to handle such kind of processes ?
In React, you usually achieve redirects in the componentDidUpdate of your components.
In the case of async actions, you will check a flag stored in the Redux store, generally a boolean like isFetching, isCreating, isUpdating, etc…, which will be modified by the actions.
Simple example:
class EditUser extends Component {
compondentDidUpdate(prevProps) {
if (prevProps.isUpdating && !this.props.isUpdating) {
// ↑ this means that the async call is done.
history.push('/users')
}
}
updateUser() {
const modifiedUser = // ...
this.props.updateUser(modifiedUser)
// ↑ will change state.users.isUpdating from false to true during the async call,
// then from true to false once the async call is done.
}
render() {
// ...
<button onClick={this.updateUser}>Update</button>
// ...
}
}
const mapStateToProps = (state, props) => ({
userToEdit: state.users.items.find(user => user.id === props.userId)
isUpdating: state.users.isUpdating,
})
const mapActionsToProps = {
updateUser: usersActions.updateUser,
}
export default connect(mapStateToProps, mapActionsToProps)(EditUser)
The next step is usually to add another flag in your Redux store to track if the async calls are successful or not (e.g. state.users.APIError, in which you can keep the error returned by the API). Then you achieve the redirect only if there are no errors.
We mostly redirect a user due to when user logged in or when sign out. For example here's basic requireAuth HOC component to check if user is logged in or not and redirect him to another place.
import React, { Component } from 'react';
import { connect } from 'react-redux';
export default ChildComponent => {
class ComposedComponent extends Component {
componentDidMount() {
this.shouldNavigateAway();
}
componentDidUpdate() {
this.shouldNavigateAway();
}
shouldNavigateAway() {
if (!this.props.auth) {
this.props.history.push('/');
}
}
render() {
return <ChildComponent {...this.props} />;
}
}
function mapStateToProps(state) {
return { auth: state.auth.authenticated };
}
return connect(mapStateToProps)(ComposedComponent);
};
There are two position to check if user is logged in
When the first time that component mount - in componentDidMount()
When user try to sign in , log in or sign out - in componentDidUpdate()
Also in your code sample, history.push is in an action creator. Action creators belongs to redux side. Keep redux & react separate.

Update redux in the reactjs function, then use the state

I need to open a search aid and select a value there and get it back.
When I click on the button, I open a search help, I put the data I selected into a store, but how can I use it when I come back?
I need to write the data I selected from the search help directly into an input on the front side.
async showPopup(){
const LazyLoadingComponent=await import('../../CP/SearchHelp/searchHelp');
this.setState({lazyLoadComponent:React.createElement(LazyLoadingComponent.default)});
await ShowPopup('http://localhost:3070/api/WorkCenter/GetWorkCenters');
console.log(this.state.selectedRow);
if(this.state.selectedRow!==''){
this.setState({WorkCenterCode:this.state.selectedRow.WorkCenterCode});
}
}
Here in some way I have to wait until the page is imported.
In the showpopup, I actually show the data that needs to be updated by updating the redux in the search help.
export async function ShowPopup(apiUrl){
var apiData=await APIGetWorkCenters(apiUrl);
SearchHelApiData(await JSON.parse(apiData.data));
SearchHelPopupOpen(true);
}
export const SearchHelPopupOpen=(popupOpen)=>{
store.dispatch({
type:'SearchHelp_popupOpen',
popupOpen:popupOpen
});
}
export const SearchHelApiData=(apiData)=>{
store.dispatch({
type:'SearchHelp_apiData',
apiData:apiData
});
}
Here I need to make my searchhelp component async and component until closing.
I share the codes of the searchhelp component below.
class SearchHelp extends BasePage {
constructor(props){
super(props);
this.connect(['SearchHelp']);
this.onSelectionChanged = this.onSelectionChanged.bind(this);
}
componentDidMount(){
SearchHelSelectedRow('');
}
toggle = () => {
SearchHelApiData('');
SearchHelPopupOpen(false);
}
onSelectionChanged({ selectedRowsData }) {
const data = selectedRowsData[0];
SearchHelSelectedRow(data);
SearchHelApiData('');
SearchHelPopupOpen(false);
}
render() {
return (
<MDBContainer>
<MDBModal size="md" isOpen={this.state.popupOpen} toggle={this.toggle} centered backdrop={false}>
<MDBModalHeader className="" toggle={this.toggle}></MDBModalHeader>
<MDBModalBody>
<DataGrid
dataSource={this.state.apiData}
selection={{ mode: 'single' }}
showBorders={true}
hoverStateEnabled={true}
keyExpr={'WorkCenterId'}
onSelectionChanged={this.onSelectionChanged} >
</DataGrid>
</MDBModalBody>
</MDBModal>
</MDBContainer>
);
}
}
I'm waiting for your help..
----------EDIT-------------
I solved my problem with the componentDidUpdate() method.
componentDidUpdate(){
if(this.state.selectedRow!=='' && this.state.selectedRow!==undefined){
SearchHelSelectedRow('');
if(this.state.selectedRow.WorkCenterId!==undefined){
this.setState({WorkCenterCode:this.state.selectedRow.WorkCenterCode});}
if(this.state.selectedRow.ReasonCode!==undefined){
this.setState({ReasonCode:this.state.selectedRow.ReasonCode});}
}
}
async showPopupWorkCenter(){
await ShowPopup('http://localhost:3070/api/WorkCenter/GetWorkCenters');
}
async showPopupReasons(){
await ShowPopup('http://localhost:3070/api/Reason/GetReasons');
}
In order for you to use Redux in your SearchHelp component and gain access to the Redux store, you need to connect your component to the store which I don't see you doing.
You need to three things basically to get things working, a reducer, actionCreator and a store to hold state changes. When you have these then you would have to connect your component to the store by using the connect higher order function which takes two arguments and wraps your component giving you access to the data stored in the store.
As an example, given your component SearchHelp, you can connect to the store by doing this:
import { connect } from 'redux'
class SearchHelp extends BasePage { ... }
function mapStateToProps(state) {
// this function has access to your redux store, so you can access the properties there
// it should return an object
return {
stateProp1: state.stateProp1,
stateProp2: state.stateProp2,
...
}
}
function mapDispatchToProps() {
// this function is not required as you could simply pass in your actions directly
}
export default connect(mapStateToProps, mapDispatchToProps)(SearchHelp)
An example reducer looks like below:
function reducerName(state = {}, action) {
switch(action.type) {
case ACTION_TYPE:
return { stateProp1: action.data // };
...
default:
return state;
}
}

Chain connect/mapStateToProps/mapDispatchToProps functions for code reuse in react-redux

Say I have two redux connected components. The first is a simple todo loading/display container, with the following functions passed to connect(); mapStateToProps reads the todos from the redux state, and mapDispatchToProps is used to request the state to be provided the latest list of todos from the server:
TodoWidgetContainer.js
import TodoWidgetDisplayComponent from '...'
function mapStateToProps(state) {
return {
todos: todoSelectors.getTodos(state)
};
}
function mapDispatchToProps(dispatch) {
return {
refreshTodos: () => dispatch(todoActions.refreshTodos())
};
}
connect(mapStateToProps, mapDispatchTo)(TodoWidgetDisplayComponent);
The second redux component is intended to be applied to any component on a page so that component can indicate whether a global "loading" icon is displayed. Since this can be used anywhere, I created a helper function that wraps MapDispatchToProps in a closure and generates an ID for each component, which is used to make sure all components that requested the loader indicate that they don't need it anymore, and the global loader can be hidden.
The functions are basically as follows, with mapStateToProps exposing the loader visibility to the components, and mapDispatchToProps allowing them to request the loader to show or hide.
Loadify.js
function mapStateToProps(state) {
return {
openLoader: loaderSelectors.getLoaderState(state)
};
}
function mapDispatchToProps() {
const uniqId = v4();
return function(dispatch) {
return {
showLoader: () => {
dispatch(loaderActions.showLoader(uniqId));
},
hideLoader: () => {
dispatch(loaderActions.hideLoader(uniqId));
}
};
};
}
export default function Loadify(component) {
return connect(mapStateToProps, mapDispatchToProps())(component);
}
So now, if I have a component that I want to give access to the loader, I can just do something like this:
import Loadify from '...'
class DisplayComponent = new React.Component { ... }
export default Loadify(DisplayComponent);
And it should give it a unique ID, allow it to request the loader to show/hide, and as long as there is one component that is requesting it to show, the loader icon will show. So far, this all appears to be working fine.
My question is, if I would like to apply this to the todos component, so that that component can request/receive its todos while also being allowed to request the loader to show while it is processing, could I just do something like:
TodoWidgetContainer.js
import Loadify from '...'
import TodoWidgetDisplayComponent from '...'
function mapStateToProps(state) {
return {
todos: todoSelectors.getTodos(state)
};
}
function mapDispatchToProps(dispatch) {
return {
refreshTodos: () => dispatch(todoActions.refreshTodos())
};
}
const TodoContainer = connect(mapStateToProps, mapDispatchTo)(TodoWidgetDisplayComponent);
export default Loadify(TodoContainer);
And will redux automatically merge the objects together to make them compatible, assuming there are no duplicate keys? Or will it take only the most recent set of mapStateToProps/mapDispatchTo unless I do some sort of manual merging? Or is there a better way to get this kind of re-usability that I'm not seeing? I'd really rather avoid having to create a custom set of containers for every component we need.
connect will automatically merge together the combination of "props passed to the wrapper component", "props from this component's mapState", and "props from this component's mapDispatch". The default implementation of that logic is simply:
export function defaultMergeProps(stateProps, dispatchProps, ownProps) {
return { ...ownProps, ...stateProps, ...dispatchProps }
}
So, if you stack multiple levels of connect around each other , the wrapped component will receive all of those props as long as they don't have the same name. If any of those props do have the same name, then only one of them would show up, based on this logic.
Alright, here is what I would do. Create a higher order component (HOC) that adds a new spinner reference to your reducer. The HOC will initialize and destroy references to the spinner in redux by tying into the life cycle methods. The HOC will provide two properties to the base component. The first is isLoading which is a function that takes a boolean parameter; true is on, false is off. The second property is spinnerState that is a readonly boolean of the current state of the spinner.
I created this example without the action creators or reducers, let me know if you need an example of them.
loadify.jsx
/*---------- Vendor Imports ----------*/
import React from 'react';
import { connect } from 'react-redux';
import v4 from 'uuid/v4';
/*---------- Action Creators ----------*/
import {
initNewSpinner,
unloadSpinner,
toggleSpinnerState,
} from '#/wherever/your/actions/are'
const loadify = (Component) => {
class Loadify extends React.Component {
constructor(props) {
super(props);
this.uniqueId = v4();
props.initNewSpinner(this.uniqueId);;
this.isLoading = this.isLoading.bind(this);
}
componentWillMount() {
this.props.unloadSpinner(this.uniqueId);
}
// true is loading, false is not loading
isLoading(isOnBoolean) {
this.props.toggleSpinner(this.uniqueId, isOnBoolean);
}
render() {
// spinners is an object with the uuid as it's key
// the value to the key is weather or not the spinner is on.
const { spinners } = this.props;
const spinnerState = spinners[this.uniqueId];
return (
<Component isLoading={this.isLoading} spinnerState={spinnerState} />
);
}
}
const mapStateTopProps = state => ({
spinners: state.ui.spinners,
});
const mapDispatchToProps = dispatch => ({
initNewSpinner: uuid => dispatch(initNewSpinner(uuid)),
unloadSpinner: uuid => dispatch(unloadSpinner(uuid)),
toggleSpinner: (uuid, isOn) => dispatch(toggleSpinnerState(uuid, isOn))
})
return connect(mapStateTopProps, mapDispatchToProps)(Loadify);
};
export default loadify;
Use Case Example
import loadify from '#/location/loadify';
import Spinner from '#/location/SpinnerComponent';
class Todo extends Component {
componentWillMount() {
this.props.isLoading(true);
asyncCall.then(response => {
// process response
this.props.isLoading(false);
})
}
render() {
const { spinnerState } = this.props;
return (
<div>
<h1>Spinner Testing Component</h1>
{ spinnerState && <Spinner /> }
</div>
);
}
}
// Use whatever state you need
const mapStateToProps = state => ({
whatever: state.whatever.youneed,
});
// use whatever dispatch you need
const mapDispatchToProps = dispatch => ({
doAthing: () => dispatch(doAthing()),
});
// Export enhanced Todo Component
export default loadify(connect(mapStateToProps, mapDispatchToProps)(Todo));

How to update state using Redux?

I am using this starter kit https://github.com/davezuko/react-redux-starter-kit and am following some tutorials at the same time, but the style of this codebase is slightly more advanced/different than the tutorials I am watching. I am just a little lost with one thing.
HomeView.js - This is just a view that is used in the router, there are higher level components like Root elsewhere I don't think I need to share that, if I do let me know, but it's all in the github link provided above.
import React, { PropTypes } from 'react'
import { connect } from 'react-redux'
import { searchListing } from '../../redux/modules/search'
export class HomeView extends React.Component {
componentDidMount () {
console.log(this.props)
}
render () {
return (
<main onClick={this.props.searchListing}>
<NavBar search={this.props.search} />
<Hero/>
<FilterBar/>
<Listings/>
<Footer/>
</main>
)
}
}
I am using connect() and passing in mapStateToProps to tell the HomeView component about the state. I am also telling it about my searchListing function that is an action which returns a type and payload.
export const searchListing = (value) => {
console.log(value)
return {
type: SEARCH_LISTINGS,
payload: value
}
}
Obviously when I call the method inside the connect() I am passing in an empty object searchListing: () => searchListing({})
const mapStateToProps = (state) => {
return {
search: { city: state.search }
}
}
export default connect((mapStateToProps), { searchListing: () => searchListing({}) })(HomeView)
This is where I am stuck, I am trying to take the pattern from the repo, which they just pass 1, I think anytime that action is created the logic is just add 1 there is no new information passed from the component.
What I am trying to accomplish is input search into a form and from the component pass the users query into the action payload, then the reducer, then update the new state with the query. I hope that is the right idea.
So if in the example the value of 1 is hardcoded and passed into the connect() method, how can I make it so that I am updating value from the component dynamically? Is this even the right thinking?
You almost got it right. Just modify the connect function to pass the action you want to call directly:
const mapStateToProps = (state) => ({
search: { city: state.search }
});
export default connect((mapStateToProps), {
searchListing
})(HomeView);
Then you may use this action with this.props.searchListing(stringToSearch) where stringToSearch is a variable containing the input value.
Notice : You don't seem to currently retrieve the user query. You may need to retrieve it first and then pass it to the searchListing action.
If you need to call a function method, use dispatch.
import { searchListing } from '../../redux/modules/search';
const mapDispatchToProps = (dispatch) => ({
searchListing: () => {
dispatch(searchListing());
}
});
export default connect(mapStateToProps, mapDispatchToProps)(HomeView);
Then, you have made the function a prop, use it with searchListing.

Categories

Resources