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;
}
}
Related
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.
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.
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));
The state gets passed in via props (selectedCategory). Using axios to get an array... it comes back, I can console.log it within the function. I'd like to assign it to a variable and then outside of the function I'd like to map over it. But it is unavailable (undefined)
import React from 'react';
import SearchCategoryMembers from '../libs/category-members';
import MemberItem from './member_item';
const Members = ({ selectedCategory }) => {
if (!selectedCategory) {
return <div className="none" />;
}
SearchCategoryMembers({ categoryTitle: selectedCategory.title }, members => {
console.log(members);
});
// members not available to map over outside of function
return <ul className="member-list" />;
};
export default Members;
What you're trying to do is a common paradigm in React. You're asking for data from some outside source, and then once it's retrieved my guess is you want to render it in this component but you're trying to use a stateless component to do it. You need a way of keeping track of when the callback in SearchCategoryMembers has completed. You have to use state.
import React, { Component } from 'react';
import SearchCategoryMembers from '../libs/category-members';
import MemberItem from './member_item';
class Members extends Component {
constructor(props) {
super(props);
// We'll put our members here once we get them back
this.state = { };
}
// This is generally where you want to make api calls in the react lifecycle
componentDidMount() {
const selectedCategory = this.props.selectedCategory;
if (!selectedCategory) return;
SearchCategoryMembers(
{ categoryTitle: selectedCategory.title },
members => {
// now we have our members data, set it on state
this.setState({ members: members });
// or fancy es6 shorthand: this.setState({ members });
}
);
}
render() {
// my guess is you want to render something about the members here
if (!this.state.members) return null;
return (
<ul>
{this.state.members.map((member) => <li>{member.name}</li>)}
</ul>
);
}
}
export default Members;
The issue is the SearchCategoryMembers is an asynchronous function so we need to wait for the data to be returned before we can use the members data.
To do this we can fetch the data from the asynchronous API request and update the state of the parent component. We'll initially set the members state to an empty array. When the component mounts we'll request the data and when it's returned we'll update the members state, which is then passed to the <Members /> component which then renders the members array passed to it.
Here's a simplified working example:
function SearchCategoryMembers() {
return axios.get('https://jsonplaceholder.typicode.com/posts');
}
class MembersWrapper extends React.Component {
constructor(props) {
super(props);
this.state = {
members: []
}
}
componentDidMount = () => {
SearchCategoryMembers().then(data => {
this.setState({
members: data.data
})
});
}
render() {
return (
<Members members={this.state.members} />
)
}
}
function Members(props) {
return (
<ul>
{props.members.map((member, id) => <li key={id}>{member.title}</li>)}
</ul>
)
}
ReactDOM.render(<MembersWrapper />, document.getElementById("app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.17.0/axios.js"></script>
<div id="app"></div>
try Members.props.selectedCategory
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.