Routing to new page in react - reactjs

I am trying to pass a parameter to a url which is provided by the on click event ,and use it to route to a different page.
Places autocomplete returns a list of suggested places and a event listener is attached to it such that whenever a user clicks on it ,It will direct the user to a new Page.
I have tried attaching Link from react-router to redirect the current page to the new location but I am unable to pass the latitude and longitude as parameter
class Searchbar extends React.Component {
constructor(props) {
super(props);
this.state = { address: '' };
}
handleChange = address => {
this.setState({ address });
};
handleSelect = address => {
geocodeByAddress(address)
.then(results => getLatLng(results[0]))
.then(latLng => console.log('Success', latLng))
.catch(error => console.error('Error', error));
};
render() {
return (
<PlacesAutocomplete
value={this.state.address}
onChange={this.handleChange}
onSelect={this.handleSelect}
>
{({ getInputProps, suggestions, getSuggestionItemProps, loading }) => (
<div>
<input
{...getInputProps({
placeholder: 'Search Places ...',
className: 'location-search-input',
})}
/>
<div className="autocomplete-dropdown-container">
{suggestions.map(suggestion => {
const className = suggestion.active
? 'suggestion-item--active'
: 'suggestion-item';
// inline style for demonstration purpose
const style = suggestion.active
? { backgroundColor: '#fafafa', cursor: 'pointer' }
: { backgroundColor: '#ffffff', cursor: 'pointer' };
return (
<div
{...getSuggestionItemProps(suggestion, {
className,
style,
})}
>
<Link to=`/${pass latitude and long here}`> <span>{suggestion.description}</span></Link> {//Not sure what to do here}
</div>
);
})}
</div>
</div>
)}
</PlacesAutocomplete>
);
}
}
export default Searchbar

Instead if using Link, redirect user whenever PlacesAutocomplete got onSelect event
import { withRouter } from 'react-router-dom'
//...
handleSelect = address => {
geocodeByAddress(address)
.then(results => getLatLng(results[0]))
.then(latLng => this.props.history.push(`/?lat=${latLng.lat}&?lat=${latLng.lng}`))
.catch(error => console.error('Error', error));
};
//...
export default withRouter(Searchbar)
Maybe you need to add loading indicator while geocodeByAddress still working

Related

Connected component doesn't re-render after store changed from another connected component

I am having a problem related to redux.
I have 2 connected components which are:
avatar situated in the navbar which is always visible
profile which is responsible for changing the avatar image in the store
if I am right, when the store change, any connected component will re-render if needed.
In my case, when the action UPDATE_CURRENT_USER update the avatar image, the navbar avatar doesn't get the new image only after I change route or reload page.
I found a solution but many people say it's a hack,
I have put a listener on store changes in the main component and did forceUpdate()
componentDidMount() {
store.subscribe(res => this.forceUpdate());
}
and I don't want to use it since connected components are supposed to re-render on store changes.
user actions:
export const getCurrentUser = () => dispatch => {
axios.get("user").then(user => {
dispatch({
type: GET_CURRENT_USER,
payload: user.data
});
});
};
export const updateCurrentUser = user => dispatch => {
dispatch({
type: UPDATE_CURRENT_USER,
payload: user
})
}
user reducer
const initialState = {
user: {}
}
export default function (state = initialState, action) {
switch (action.type) {
case GET_CURRENT_USER:
return { ...state, user: action.payload };
case UPDATE_CURRENT_USER:
return { ...state, user: action.payload }
default:
return state;
}
}
profile component
class Profile extends Component {
render() {
const { currentUser, updateCurrentUser } = this.props;
return (
<div id="profile-container">
<ProfileSider
currentUser={currentUser}
updateCurrentUser={updateCurrentUser}
/>
<ProfileContent
currentUser={currentUser}
updateCurrentUser={updateCurrentUser}
/>
</div>
);
}
}
const mapStateToProps = state => ({
currentUser: state.userReducer.user
});
export default connect(
mapStateToProps,
{ updateCurrentUser }
)(Profile);
profile sidebar child of profile
class ProfileSider extends Component {
state = { uploading: false };
triggerAvatarInput() {
$("#avatarInput").click();
}
handleChange = async event => {
this.setState({ ...this.state, uploading: true });
const avatarFormData = new FormData();
avatarFormData.append("file", event.target.files[0]);
axios
.post("uploadFile", avatarFormData)
.then(res => {
const avatarURIFormData = new FormData();
avatarURIFormData.append("avatar", res.data.fileDownloadUri);
axios
.put("user/update", avatarURIFormData)
.then(res => {
const { currentUser } = this.props;
currentUser.avatar = res.data.avatar;
this.props.updateCurrentUser(currentUser);
this.setState({
...this.state,
uploading: false,
avatar: currentUser.avatar
});
message.success("Avatar updated successfully", 3);
})
.catch(error => {
this.setState({ ...this.state, uploading: false });
message.error("Updating avatar failed!", 3);
});
})
.catch(error => {
this.setState({ ...this.state, uploading: false });
message.error("Uploading avatar failed!", 3);
});
};
render() {
const { uploading } = this.state;
const { currentUser } = this.props;
return (
<div id="profile-sider">
<div id="profile-sider-info">
<div id="profile-sider-info-avatar">
<div className="container">
<div
className="overlay-uploading"
className={
uploading ? "overlay-uploading" : "overlay-uploading hidden"
}
>
<Icon type="loading" style={{ fontSize: 50, color: "#FFF" }} />
</div>
<div className="overlay" />
<div className="overlay-text" onClick={this.triggerAvatarInput}>
<Icon type="camera" style={{ fontSize: 20 }} />
<span>Update</span>
</div>
<div
className="avatar"
style={{
backgroundImage: "url(" + currentUser.avatar + ")"
}}
></div>
<input
onChange={this.handleChange}
type="file"
accept="image/png, image/jpeg, image/jpg"
id="avatarInput"
/>
</div>
</div>
<h2 style={{ marginTop: 20, textAlign: "center" }}>
{currentUser.fullName}
</h2>
<h4 style={{ textAlign: "center" }}>{currentUser.email}</h4>
</div>
<div id="profile-sider-actions">
<div className="profile-sider-actions-item">
<Link to="/profile/courses" style={{ transition: 0 }}>
<Button type="primary" id="courses-btn">
<Icon type="read" style={{ marginRight: 15 }} />
My Courses
</Button>
</Link>
</div>
<div className="profile-sider-actions-item">
<Link to="/profile/update">
<Button type="primary" id="update-infos-btn">
<Icon type="sync" style={{ marginRight: 15 }} />
Update Infos
</Button>
</Link>
</div>
</div>
</div>
);
}
}
export default ProfileSider;
avatar component situated in navbar
class ProfileAvatar extends Component {
constructor() {
super();
this.handleClick = this.handleClick.bind(this);
this.handleOutsideClick = this.handleOutsideClick.bind(this);
this.state = {
showProfileDropdown: false
};
}
componentDidMount() {
this.props.getCurrentUser();
}
handleLogout = async () => {
try {
await auth.logout();
this.props.onLogout();
notification["success"]({
message: "You have been successfully logged out!"
});
} catch (ex) {}
};
handleClick() {
if (!this.state.showProfileDropdown) {
// attach/remove event handler
document.addEventListener("click", this.handleOutsideClick, false);
} else {
document.removeEventListener("click", this.handleOutsideClick, false);
}
this.setState(prevState => ({
showProfileDropdown: !prevState.showProfileDropdown
}));
}
handleOutsideClick(e) {
// ignore clicks on the component itself
if (this.element && this.element.contains(e.target)) {
return;
}
this.handleClick();
}
render() {
const { currentUser } = this.props;
return (
<div
className="profile-avatar"
ref={element => {
this.element = element;
}}
>
<Avatar
onClick={this.handleClick}
size="large"
style={{ color: "#f56a00", backgroundColor: "#fde3cf" }}
src={currentUser.avatar}
>
{currentUser.fullName ? currentUser.fullName.charAt(0) : null}
</Avatar>
{this.state.showProfileDropdown && (
<div className="profile-dropdown-list">
<List
className="dropdown_list dropdown-shadow "
size="small"
style={{ width: "150px" }}
bordered
itemLayout="vertical"
dataSource={[
<Link to="/profile/update" className="profile-list-item">
<List.Item className="list-item">
<Icon className="profile-icons" type="user" /> My Profile
</List.Item>
</Link>,
<Link to="/profile/courses" className="profile-list-item">
<List.Item className="list-item">
<Icon className="profile-icons" type="container" /> My
Courses
</List.Item>
</Link>,
<List.Item className="list-item">
<Icon className="profile-icons" type="question-circle" /> Ask
for Help
</List.Item>,
<List.Item className="list-item" onClick={this.handleLogout}>
<Icon className="profile-icons" type="logout" /> Log out
</List.Item>
]}
renderItem={item => item}
/>
</div>
)}
</div>
);
}
}
const mapStateToProps = state => ({
currentUser: state.userReducer.user
});
export default connect(
mapStateToProps,
{ getCurrentUser }
)(ProfileAvatar);
image: https://imge.to/i/vywTNj
There are two problems here:
You are mutating the existing object from the store
You are sending that exact same user object back into the store when you dispatch the action.
Specifically, these lines are the cause:
const { currentUser } = this.props;
currentUser.avatar = res.data.avatar;
this.props.updateCurrentUser(currentUser);
currentUser is the user object that's already in the Redux store. This code mutates the object, and inserts it back into the store.
That results in the connected component thinking nothing has actually changed.
The shortest way to fix this is to create a new user object, and insert that:
const {currentUser} = this.props;
const updatedUser = {...currentUser, avatar: res.data.avatar};
this.props.updateCurrentUser(updatedUser);
To avoid this in the future, I strongly encourage you to use the configureStore function from our Redux Starter Kit package, which detects mutations and will throw errors if you mutate.

Invalid hook call with useCallback in react

I have a problem, I tried to transform in a class this function from this example :
https://codesandbox.io/s/5vn3lvz2n4
But, I got an error. Here is the code :
import React, { Component, useCallback } from "react";
import Gallery from "react-photo-gallery";
import Carousel, { Modal, ModalGateway } from "react-images";
class Gallerie extends Component {
constructor(props){
super(props)
this.state = {
currentImage: 0,
setCurrentImage: 0,
viewerIsOpen: false,
setViewerIsOpen: false,
}
}
render(){
const openLightbox = useCallback((event, { photo, index }) => {
this.state.setCurrentImage(index);
this.state.setViewerIsOpen(true);
}, []);
const closeLightbox = () => {
this.state.setCurrentImage(0);
this.state.setViewerIsOpen(false);
};
return (
<div>
<Gallery photos={this.props.photos} onClick={() => openLightbox} />
<ModalGateway>
{this.state.viewerIsOpen ? (
<Modal onClose={() =>closeLightbox}>
<Carousel
currentIndex={this.state.currentImage}
views={this.props.photos.map(x => ({
...x,
srcset: x.srcSet,
caption: x.title
}))}
/>
</Modal>
) : null}
</ModalGateway>
</div>
);
}
}
export default Gallerie;
Here is the problem and what I got :
I don't exaclty know what the useCallBack does. If I just copy / paste the example, it works. The problem is that the variable "photos" is used as props cause it will be different for each user. So I need it into other components. If I use a function like the example, I can't use props...
Thanks for your help.
You're using a hook inside a class based component. First convert it to a functional component
const Gallerie = props =>{
const [state, setState] = useState({
currentImage: 0,
setCurrentImage: 0,
viewerIsOpen: false,
setViewerIsOpen: false,
})
const openLightbox = useCallback((event, { photo, index }) => {
setState({...state, setCurrentImage: index, setViewerIsOpen: true});
}, []);
const closeLightbox = () => {
setState({...state, setCurrentImage: 0,setViewerIsOpen: false })
};
return (
<div>
<Gallery photos={props.photos} onClick={() => openLightbox} />
<ModalGateway>
{state.viewerIsOpen ? (
<Modal onClose={() =>closeLightbox}>
<Carousel
currentIndex={state.currentImage}
views={props.photos.map(x => ({
...x,
srcset: x.srcSet,
caption: x.title
}))}
/>
</Modal>
) : null}
</ModalGateway>
</div>
);
}

How to use my react component PlaceInput to achieve place autocomplete in the menu input box?

I have a PlaceInput component which support google place autocomplete.
class PlaceInput extends Component {
state = {
scriptLoaded: false
};
handleScriptLoaded = () => this.setState({ scriptLoaded: true });
render() {
const {
input,
width,
onSelect,
placeholder,
options,
meta: { touched, error }
} = this.props;
return (
<Form.Field error={touched && !!error} width={width}>
<Script
url="https://maps.googleapis.com/maps/api/js?key={my google api key}&libraries=places"
onLoad={this.handleScriptLoaded}
/>
{this.state.scriptLoaded &&
<PlacesAutocomplete
inputProps={{...input, placeholder}}
options={options}
onSelect={onSelect}
styles={styles}
/>}
{touched && error && <Label basic color='red'>{error}</Label>}
</Form.Field>
);
}
}
export default PlaceInput;
I also have a menu item which is an<Input> from semantic-ui-react. The frontend is like below:
The menu code is like below:
<Menu.Item>
<Input
icon='marker'
iconPosition='left'
action={{
icon: "search",
onClick: () => this.handleClick()}}
placeholder='My City'
/>
</Menu.Item>
How can I leverage the PlaceInput component to make menu <Input> box to achieve the place autocomplete? Thanks!
If you could share a working sample of your app (in e.g. codesandbox) I should be able to help you make your PlaceInput class work with the Menu.Input from semantic-ui-react.
Otherwise, you can test a fully working example of such integration with the code below, which is based off of the Getting Started code from react-places-autocomplete.
import React from "react";
import ReactDOM from "react-dom";
import PlacesAutocomplete, {
geocodeByAddress,
getLatLng
} from "react-places-autocomplete";
import { Input, Menu } from "semantic-ui-react";
const apiScript = document.createElement("script");
apiScript.src =
"https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&libraries=places";
document.head.appendChild(apiScript);
const styleLink = document.createElement("link");
styleLink.rel = "stylesheet";
styleLink.href =
"https://cdn.jsdelivr.net/npm/semantic-ui/dist/semantic.min.css";
document.head.appendChild(styleLink);
class LocationSearchInput extends React.Component {
constructor(props) {
super(props);
this.state = { address: "" };
}
handleChange = address => {
this.setState({ address });
};
handleSelect = address => {
geocodeByAddress(address)
.then(results => getLatLng(results[0]))
.then(latLng => console.log("Success", latLng))
.catch(error => console.error("Error", error));
};
render() {
return (
<PlacesAutocomplete
value={this.state.address}
onChange={this.handleChange}
onSelect={this.handleSelect}
>
{({ getInputProps, suggestions, getSuggestionItemProps, loading }) => (
<div>
<Menu>
<Menu.Item>
<Input
icon="marker"
iconPosition="left"
placeholder="My City"
{...getInputProps({
placeholder: "Search Places ...",
className: "location-search-input"
})}
/>
</Menu.Item>
</Menu>
<div className="autocomplete-dropdown-container">
{loading && <div>Loading...</div>}
{suggestions.map(suggestion => {
const className = suggestion.active
? "suggestion-item--active"
: "suggestion-item";
// inline style for demonstration purpose
const style = suggestion.active
? { backgroundColor: "#fafafa", cursor: "pointer" }
: { backgroundColor: "#ffffff", cursor: "pointer" };
return (
<div
{...getSuggestionItemProps(suggestion, {
className,
style
})}
>
<span>{suggestion.description}</span>
</div>
);
})}
</div>
</div>
)}
</PlacesAutocomplete>
);
}
}
ReactDOM.render(<LocationSearchInput />, document.getElementById("root"));
Hope this helps!

React AntDesign add uploaded images to FormData

I want to use image uploader from AntDesign library. Here is a snapshot
import { Upload, Icon, Modal } from 'antd'
class PicturesWall extends React.Component {
state = {
previewVisible: false,
previewImage: '',
fileList: [],
}
handleCancel = () => this.setState({ previewVisible: false })
handlePreview = file => {
this.setState({
previewImage: file.url || file.thumbUrl,
previewVisible: true,
})
}
handleChange = ({ fileList }) => this.setState({ fileList })
render() {
const { previewVisible, previewImage, fileList } = this.state
const uploadButton = (
<div>
<Icon type="plus" />
<div className="ant-upload-text">Upload</div>
</div>
)
return (
<div className="clearfix">
<Upload
action="//jsonplaceholder.typicode.com/posts/"
listType="picture-card"
fileList={fileList}
onPreview={this.handlePreview}
onChange={this.handleChange}
>
{fileList.length >= 3 ? null : uploadButton}
</Upload>
<Modal
visible={previewVisible}
footer={null}
onCancel={this.handleCancel}
>
<img alt="example" style={{ width: '100%' }} src={previewImage} />
</Modal>
</div>
)
}
}
ReactDOM.render(<PicturesWall />, mountNode)
It's so hard to understand what's going on here for me .
Can I get images from this component using something like
const img=event.target.files[0];?
All I want it's put that uploaded images into array and using FormData send axios.post request to the backend.
I'm newbie in React .If it's something obvious pardon me.Thank you in advance
antd's Upload component is doing the upload for you under the hood. But if you don't want to do that, and upload the file later, you can also achieve that with the help of beforeUpload prop.
From the docs:
beforeUpload: Hook function which will be executed before uploading. Uploading will be stopped with false or a rejected Promise returned. Warning:this function is not supported in IE9
I have written an example and added comments where necessary:
class PicturesWall extends React.Component {
state = {
previewVisible: false,
previewImage: "",
fileList: []
};
handleCancel = () => this.setState({ previewVisible: false });
handlePreview = file => {
this.setState({
previewImage: file.thumbUrl,
previewVisible: true
});
};
handleUpload = ({ fileList }) => {
//---------------^^^^^----------------
// this is equivalent to your "const img = event.target.files[0]"
// here, antd is giving you an array of files, just like event.target.files
// but the structure is a bit different that the original file
// the original file is located at the `originFileObj` key of each of this files
// so `event.target.files[0]` is actually fileList[0].originFileObj
console.log('fileList', fileList);
// you store them in state, so that you can make a http req with them later
this.setState({ fileList });
};
handleSubmit = event => {
event.preventDefault();
let formData = new FormData();
// add one or more of your files in FormData
// again, the original file is located at the `originFileObj` key
formData.append("file", this.state.fileList[0].originFileObj);
axios
.post("http://api.foo.com/bar", formData)
.then(res => {
console.log("res", res);
})
.catch(err => {
console.log("err", err);
});
};
render() {
const { previewVisible, previewImage, fileList } = this.state;
const uploadButton = (
<div>
<Icon type="plus" />
<div className="ant-upload-text">Upload</div>
</div>
);
return (
<div>
<Upload
listType="picture-card"
fileList={fileList}
onPreview={this.handlePreview}
onChange={this.handleUpload}
beforeUpload={() => false} // return false so that antd doesn't upload the picture right away
>
{uploadButton}
</Upload>
<Button onClick={this.handleSubmit} // this button click will trigger the manual upload
>
Submit
</Button>
<Modal
visible={previewVisible}
footer={null}
onCancel={this.handleCancel}
>
<img alt="example" style={{ width: "100%" }} src={previewImage} />
</Modal>
</div>
);
}
}
ReactDOM.render(<PicturesWall />, document.getElementById("container"));

react-select prevent menu to open onInputChange

I'm trying to use the react-select component as an input and a select component.
Doing so I would like to prevent the menu to open while the user is typing the input.
I just can't find a way to update this behavior by either a prop or updating the onInputChange method.
My problem if I decide to use a controlled state with the menuIsOpen prop is that I can't manage to reopen the Menu control is clicked.
Here is what I have so far, do you guys any idea of how this could be achieved ?
<StyledSelect
components={{ IndicatorSeparator }}
{..._.omit(this.props, [])}
filterOption={() => true}
inputValue={inputValue}
onChange={value => {
this.select.setState({ menuIsOpen: false });
this.setState({ selectValue: value });
}}
onInputChange={(value, event) => {
if (event && event.action === 'input-change') {
this.setState({ inputValue: value });
}
}}
openMenuOnClick={false}
/>
Example
I think you are in the right direction using onInputChange and I would add a controlled menuIsOpen props like the following code:
class App extends Component {
constructor(props) {
super(props);
this.state = {
menuIsOpen: false
};
}
openMenu = () => {
this.setState({ menuIsOpen: !this.state.menuIsOpen });
};
onInputChange = (props, { action }) => {
if (action === "menu-close") this.setState({ menuIsOpen: false });
};
render() {
const DropdownIndicator = props => {
return (
components.DropdownIndicator && (
<div
onClick={this.openMenu}
style={{ display: "flex", alignItems: "center" }}
>
<span style={{ marginLeft: 12 }}>kg</span>
<components.DropdownIndicator
{...props}
onClick={() => {
console.log("ici");
}}
/>
</div>
)
);
};
return (
<Select
components={{ DropdownIndicator }}
options={options}
onChange={this.onChange}
onInputChange={this.onInputChange}
menuIsOpen={this.state.menuIsOpen}
/>
);
}
}
The idea with this code is to fire an onClick event on the DropdownIndicator custom component.
Here a live example.

Resources