How to initialize a StandaloneSearchBox component with a given address? - reactjs

I have a react component for a google.maps.places.SearchBox without the map, which is a StandaloneSearchBox. I want to pass it props with the initial value (which is only the formated version of the address, e.g "London, Kentucky, États-Unis") and then be able to change the address in the input field.
I have a places property in the state, which I want to hold the place object. How can I pass in the beginning in the componentDidMount() method the initial value so I can set it to the places object? It doesn't work in this way.
const PlacesWithStandaloneSearchBox = compose(
withProps({
googleMapURL: googleMapsURI,
loadingElement: <div style={{ height: `100%` }} />,
containerElement: <div style={{ height: `400px` }} />
}),
lifecycle({
componentWillMount() {
console.log("componentWillMount");
const refs = {};
this.setState({
places: [],
onSearchBoxMounted: ref => {
refs.searchBox = ref;
},
onPlacesChanged: () => {
const places = refs.searchBox.getPlaces();
this.setState({
places
});
}
})
},
componentDidMount() {
this.setState({
places: this.props.value
});
}
}),
withScriptjs
)(props =>
<div className="fretlink-input form-group" data-standalone-searchbox="">
<StandaloneSearchBox
ref={ props.onSearchBoxMounted }
bounds={ props.bounds }
onPlacesChanged={ props.onPlacesChanged }>
<input
className="form-control"
placeholder={ props.placeholder }
type="text" />
</StandaloneSearchBox>
<ol>
{ props.places.map(({ place_id, formatted_address, geometry: { location } }) =>
<li key={ place_id }>
{ formatted_address }
{" at "}
({location.lat()}, {location.lng()})
</li>
)}
</ol>
</div>
);
export default PlacesWithStandaloneSearchBox;

It appears that initializing the StandaloneSearchBox with an actual Google Maps place object, based on a search on address text or lat lng, isn't possible out of the box with a component prop or otherwise.
I think the best you can do is implement this yourself, by manually doing a search with the Google Maps places API with whatever data you have, getting that initial place, and setting it via this.setState in componentDidMount as in your example.
However, I didn't do this, because I found a solution to this that's a lot simpler, and I think will also apply in your case.
If you have the formatted address, there may actually be no reason to perform the search and populate the component with a full Google Maps place at all. All you really need is that text showing in the input. When you search for a new address, sure, you need all the data (lat lng etc) -- but that is already working. For the initial address, you probably already have this data. It's really just what you show in the <input> that's your concern.
Fortunately, it's really easy to separate the behaviour of the <input> from the behaviour of the StandaloneSearchBox because the <input> is just a child component that you can fully control.
What I did was just to use some more component state to hold the text that the input should show, and then make it a controlled component. Everything else works in exactly the same way, it's just that I take control of the 'view' which is the text displayed in the <input>.
Here is a rough example of what I mean, using your code:
// init the state with your initial value "1 Something Street, etc"
const [text, setText] = useState(INITIAL_VALUE_HERE)
<StandaloneSearchBox
ref={ props.onSearchBoxMounted }
bounds={ props.bounds }
// have onPlacesChanged set the state when a new option is picked
// to whatever the address text of the selected place is
// (needs to be implemented appropriately in onPlacesChanged of course)
onPlacesChanged={() => { props.onPlacesChanged(setText) }}
>
<input
className="form-control"
placeholder={ props.placeholder }
type="text"
value={text} // control the input value with state
onChange={(e) => { setText(e.target.value) }} // update the state when you type something
/>
</StandaloneSearchBox>

You can check my answer here: Initialize React Google Maps StandaloneSearchBox with geocode
In general you can't use address as a filter for SearchBox results, but you can specify the area in which to search for places:
<StandaloneSearchBox
ref={props.onSearchBoxMounted}
bounds={props.bounds}
onPlacesChanged={props.onPlacesChanged}
defaultBounds={new google.maps.LatLngBounds(
new google.maps.LatLng(40.712216, -74.22655),
new google.maps.LatLng(40.773941, -74.12544)
)}

Related

Trying to implement local storage in React. It is not tracking the input value that I send

I am trying to implement a local storage in REACT in order to save in a database the data(numbers) that I type in the input. The code works when I check it in the console.log but it does not work as I wish. Everytime I add a value it deletes the previous one. So I can just read the last input that I put. It does not shows me all the inputs that I put before. I would like to mention that this component is rendered dinamycally so in the parent component I get four different buttons where I can type the number that I want to. Thanks in advance
import React, { useState, useEffect } from 'react';
function Stake() {
const [stakes, setStakes] = useState([]);
const addStake = (e) => {
e.preventDefault();
const newStake = {
input: e.target.stake.value,
};
setStakes([...stakes, newStake]);
};
useEffect(() => {
const json = JSON.stringify(stakes);
localStorage.setItem("stakes", json);
}, [stakes]);
console.log(stakes)
return (
<div>
<form onSubmit={addStake}>
<input style={{ marginLeft: "40px", width: "50px" }} type="text" name="stake" required />
{/* <button style={{ marginLeft: "40px" }} type="submit">OK</button> */}
</form>
</div>
);
}
export default Stake;
Right now your component always starts with an empty array: useState([]).
When the component renders for the first time, you need to retrieve existing values from locaStorage and set it as the local state of component:
useEffect(() => {
const stakes= JSON.parse(localStorage.getItem("stakes")) || [];
setStakes(stakes);
}, []);

Discordance between component state and its props

I am making a basic drag and drop utility where you can drag and recombine the position of three cards in React.
I dont know why but there seems to be discordance between the actual data on the parents state and its child. After some refactoring following users input, things are as this:
onDrop = (event) => {
event.preventDefault();
event.stopPropagation();
const newData = this.props.getDraggedCard(); //gets the data actually being dragged around
let receiverCard = Object.assign({}, this.state) //backup of the previous state
this.setState({
_id: newData._id,
text: newData.text,
title: newData.title,
subtitle: newData.subtitle,
image: newData.image
}, () => { this.props.onDropParentData(receiverCard) })
}
this function is on every child component, which are the items actually to be moved around.
The parent component, which stores and saves the data:
onDropParentData = (receiverCard) => {
let {slideInformation}=this.state; //the state where all the data is
let draggedCard = this.state.draggedCard; //the data being dragged
const indexDragged= slideInformation.findIndex((element)=>element.id===draggedCard.id)
const indexReceiver= slideInformation.findIndex((element)=>element.id===receiverCard.id)
let draggedCardIdStorage = this.state.draggedCard.id;
draggedCard.id = receiverCard.id;
receiverCard.id = draggedCardIdStorage;
slideInformation.splice(indexDragged,1,draggedCard);
slideInformation.splice(indexReceiver,1,receiverCard);
this.setState({slideInformation});
}
with this actual code, the state holds the correct information but when dragging and dropping, items dont switch between positions as desired, only the dropped item gets its new position.
The render method on the parent is:
{this.state.slideInformation.map((slide,index) => {
return (
<CardFormBody
getDraggedCard={this.getDraggedCard}
key={index}
onDropParentData={this.onDropParentData}
onDrag={this.onDrag}
slide={slide}
onDragOver={this.onDragOver}
setFormData={this.setFormData}
/>
)
})}
And every CardFormBody is
<div draggable onDrag={(event) => { this.props.onDrag(event, this.state) }} className="newsCard" name={this.state.id}>
<label>Título</label>
<input name={this.state.id} onChange={this.handleTitleChange} value={this.props.slide.title} />
<label>Subtítulo</label>
<input name={this.state.id} onChange={this.handleSubtitleChange} value={this.state.subtitle} />
<label>Texto</label>
<textarea onDragOver={this.props.onDragOver} onDrop={this.onDrop} name={this.state.id} onChange={this.handleTextChange} value={this.state.text} />
<button type="button" name={this.state.id} onClick={this.handleImgChange}>Añadir Imagen</button>
<div className="imageWrapperForm">
<img alt="preview" name={this.state.id} className="previewImg" src={this.state.image} />
</div>
</div>
Ciao, can you provide more code? How you pass data from child to parent? Anyway I think the misalignment between children and parent data could be on how you pass them.

React google location doesnt work with Hooks

I was trying the react google api in my project. I created the count in Google Cloud and i have the api.
I have this code:
import { GoogleComponent } from "react-google-location";
export default function Home() {
const API_KEY_GOOGLE = "XXXXX";
return (
<div className="home">
<h1>GHome</h1>
<div>
<GoogleComponent
apiKey={API_KEY_GOOGLE}
language={"en"}
country={"country:in|country:us"}
coordinates={true}
// locationBoxStyle={"custom-style"}
// locationListStyle={"custom-style-list"}
onChange={e => {
setPlace({ place: e });
}}
/>
</div>
</div>
);
}
And appears that in the console:
If the application does not contain any other part where you have for example <ul> and <li> combinations or generating other HTML elements with map() without added key attribute for example then the issue is most probably with passed country attribute.
Based on the documentation of react-google-location:
Use multiple languages
Pass key and value pair in the country object like this:
country={in|country:pr|country:vi|country:gu|country:mp}
Your country attribute has {"country:in|country:us"}. Maybe it is worth to test as the following:
<GoogleComponent
apiKey={API_KEY_GOOGLE}
language={"en"}
country={"in|country:us"}
coordinates={true}
onChange={e => {
setPlace({ place: e });
}} />
Or my second guess:
<GoogleComponent
apiKey={API_KEY_GOOGLE}
language={"en"}
country={"in|country:in|country:us"}
coordinates={true}
onChange={e => {
setPlace({ place: e });
}} />
I hope that helps!

Why is the DOM not updating with state change in ReactJS?

I want to update the font size of an element using a slider. When I slide the slider, the value changes and I can successfully store that into my state using e.target.value.
But the problem is the font size does not update as I expect. I can't figure out why?
Here is my React component code:
import React, { useState } from 'react';
function App() {
const [slider, setSlider] = useState(20);
const handleChange = (e) => {
setSlider( e.target.value );
console.log(slider);
}
return (
<div className="container">
<label style={ { fontSize: slider } }>Example range</label>
<input
type="range"
className="custom-range"
id="customRange1"
min="10"
max="30"
defaultValue={slider}
onChange={handleChange}
/>
</div>
);
}
export default App;
If you replace defaultValue with the value prop, and update your <label> elements style as shown below, then your component will work as expected:
function App() {
const [slider, setSlider] = useState(20);
const handleChange = (e) => {
setSlider( e.target.value );
console.log(slider);
}
return (<div className="container">
{/* Format valid value for fontSize property */ }
<label style={{ fontSize: `${slider}px` }}>Example range</label>
{/* Supply slider state via value prop rather than defaultValue */ }
<input
type="range"
className="custom-range"
id="customRange1"
min="10"
max="30"
value={slider}
onChange={handleChange}
/>
</div>
);
}
See this link for relevant documentation discussing form/inputs elements that have a value prop specified (referred to as "Controlled components")
You miss the unit here,
<label style={ { fontSize: `${slider}px` } }>Example range</label>
Demo
const [slider, setSlider] = useState(20);
Here, you are assigning Number to slider and then updating it with string (console.log(typeof e.target.value)) i.e. you are changing data type of something, which is not good practice to code. Because it may affect your code, if you are dependent on it(slider) at more than one places, your program may break and you would never find such mistakes, follow good coding practices.
Try using setSlider(Number(e.target.value)); instead of setSlider(e.target.value);
https://codesandbox.io/s/pedantic-black-x6uzx
Example range
If you mention without unit it will take the bydefault fontsize.In your case the fontsize is updating in slider but that is not usable because unit is missing.

What is the difference betwen value and defaultValue in react.js [duplicate]

I'm trying to create a simple form with react, but facing difficulty having the data properly bind to the defaultValue of the form.
The behavior I'm looking for is this:
When I open my page, the Text input field should be filled in with the text of my AwayMessage in my database. That is "Sample Text"
Ideally I want to have a placeholder in the Text input field if the AwayMessage in my database has no text.
However, right now, I'm finding that the Text input field is blank every time I refresh the page. (Though what I type into the input does save properly and persist.) I think this is because the input text field's html loads when the AwayMessage is an empty object, but doesn't refresh when the awayMessage loads. Also, I'm unable to specify a default value for the field.
I removed some of the code for clarity (i.e. onToggleChange)
window.Pages ||= {}
Pages.AwayMessages = React.createClass
getInitialState: ->
App.API.fetchAwayMessage (data) =>
#setState awayMessage:data.away_message
{awayMessage: {}}
onTextChange: (event) ->
console.log "VALUE", event.target.value
onSubmit: (e) ->
window.a = #
e.preventDefault()
awayMessage = {}
awayMessage["master_toggle"]=#refs["master_toggle"].getDOMNode().checked
console.log "value of text", #refs["text"].getDOMNode().value
awayMessage["text"]=#refs["text"].getDOMNode().value
#awayMessage(awayMessage)
awayMessage: (awayMessage)->
console.log "I'm saving", awayMessage
App.API.saveAwayMessage awayMessage, (data) =>
if data.status == 'ok'
App.modal.closeModal()
notificationActions.notify("Away Message saved.")
#setState awayMessage:awayMessage
render: ->
console.log "AWAY_MESSAGE", this.state.awayMessage
awayMessageText = if this.state.awayMessage then this.state.awayMessage.text else "Placeholder Text"
`<div className="away-messages">
<div className="header">
<h4>Away Messages</h4>
</div>
<div className="content">
<div className="input-group">
<label for="master_toggle">On?</label>
<input ref="master_toggle" type="checkbox" onChange={this.onToggleChange} defaultChecked={this.state.awayMessage.master_toggle} />
</div>
<div className="input-group">
<label for="text">Text</label>
<input ref="text" onChange={this.onTextChange} defaultValue={awayMessageText} />
</div>
</div>
<div className="footer">
<button className="button2" onClick={this.close}>Close</button>
<button className="button1" onClick={this.onSubmit}>Save</button>
</div>
</div>
my console.log for AwayMessage shows the following:
AWAY_MESSAGE Object {}
AWAY_MESSAGE Object {id: 1, company_id: 1, text: "Sample Text", master_toggle: false}
Another way of fixing this is by changing the key of the input.
<input ref="text" key={this.state.awayMessage ? 'notLoadedYet' : 'loaded'} onChange={this.onTextChange} defaultValue={awayMessageText} />
Update:
Since this get upvotes, I will have to say that you should properly have a disabled or readonly prop while the content is loading, so you don't decrease the ux experience.
And yea, it is most likely a hack, but it gets the job done.. ;-)
defaultValue is only for the initial load
If you want to initialize the input then you should use defaultValue, but if you want to use state to change the value then you need to use value. Personally I like to just use defaultValue if I'm just initializing it and then just use refs to get the value when I submit. There's more info on refs and inputs on the react docs, https://facebook.github.io/react/docs/forms.html and https://facebook.github.io/react/docs/working-with-the-browser.html.
Here's how I would rewrite your input:
awayMessageText = if this.state.awayMessage then this.state.awayMessage.text else ''
<input ref="text" onChange={this.onTextChange} placeholder="Placeholder Text" value={#state.awayMessageText} />
Also you don't want to pass placeholder text like you did because that will actually set the value to 'placeholder text'. You do still need to pass a blank value into the input because undefined and nil turns value into defaultValue essentially. https://facebook.github.io/react/tips/controlled-input-null-value.html.
getInitialState can't make api calls
You need to make api calls after getInitialState is run. For your case I would do it in componentDidMount. Follow this example, https://facebook.github.io/react/tips/initial-ajax.html.
I'd also recommend reading up on the component lifecycle with react. https://facebook.github.io/react/docs/component-specs.html.
Rewrite with modifications and loading state
Personally I don't like to do the whole if else then logic in the render and prefer to use 'loading' in my state and render a font awesome spinner before the form loads, http://fortawesome.github.io/Font-Awesome/examples/. Here's a rewrite to show you what I mean. If I messed up the ticks for cjsx, it's because I normally just use coffeescript like this, .
window.Pages ||= {}
Pages.AwayMessages = React.createClass
getInitialState: ->
{ loading: true, awayMessage: {} }
componentDidMount: ->
App.API.fetchAwayMessage (data) =>
#setState awayMessage:data.away_message, loading: false
onToggleCheckbox: (event)->
#state.awayMessage.master_toggle = event.target.checked
#setState(awayMessage: #state.awayMessage)
onTextChange: (event) ->
#state.awayMessage.text = event.target.value
#setState(awayMessage: #state.awayMessage)
onSubmit: (e) ->
# Not sure what this is for. I'd be careful using globals like this
window.a = #
#submitAwayMessage(#state.awayMessage)
submitAwayMessage: (awayMessage)->
console.log "I'm saving", awayMessage
App.API.saveAwayMessage awayMessage, (data) =>
if data.status == 'ok'
App.modal.closeModal()
notificationActions.notify("Away Message saved.")
#setState awayMessage:awayMessage
render: ->
if this.state.loading
`<i className="fa fa-spinner fa-spin"></i>`
else
`<div className="away-messages">
<div className="header">
<h4>Away Messages</h4>
</div>
<div className="content">
<div className="input-group">
<label for="master_toggle">On?</label>
<input type="checkbox" onChange={this.onToggleCheckbox} checked={this.state.awayMessage.master_toggle} />
</div>
<div className="input-group">
<label for="text">Text</label>
<input ref="text" onChange={this.onTextChange} value={this.state.awayMessage.text} />
</div>
</div>
<div className="footer">
<button className="button2" onClick={this.close}>Close</button>
<button className="button1" onClick={this.onSubmit}>Save</button>
</div>
</div>
That should about cover it. Now that is one way to go about forms which uses state and value. You can also just use defaultValue instead of value and then use refs to get the values when you submit. If you go that route I would recommend you have an outer shell component (usually referred to as high order components) to fetch the data and then pass it to the form as props.
Overall I'd recommend reading the react docs all the way through and do some tutorials. There's lots of blogs out there and http://www.egghead.io had some good tutorials. I have some stuff on my site as well, http://www.openmindedinnovations.com.
it's extremely simple, make defaultValue and key the same:
<input defaultValue={myVal} key={myVal}/>
This is one of the recommended approaches at https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#recommendation-fully-uncontrolled-component-with-a-key
To force the defaultValue to re-render all you need to do is change the key value of the input itself. here is how you do it.
<input
type="text"
key={myDynamicKey}
defaultValue={myDynamicDefaultValue}
placeholder="It works"/>
Maybe not the best solution, but I'd make a component like below so I can reuse it everywhere in my code. I wish it was already in react by default.
<MagicInput type="text" binding={[this, 'awayMessage.text']} />
The component may look like:
window.MagicInput = React.createClass
onChange: (e) ->
state = #props.binding[0].state
changeByArray state, #path(), e.target.value
#props.binding[0].setState state
path: ->
#props.binding[1].split('.')
getValue: ->
value = #props.binding[0].state
path = #path()
i = 0
while i < path.length
value = value[path[i]]
i++
value
render: ->
type = if #props.type then #props.type else 'input'
parent_state = #props.binding[0]
`<input
type={type}
onChange={this.onChange}
value={this.getValue()}
/>`
Where change by array is a function accessing hash by a path expressed by an array
changeByArray = (hash, array, newValue, idx) ->
idx = if _.isUndefined(idx) then 0 else idx
if idx == array.length - 1
hash[array[idx]] = newValue
else
changeByArray hash[array[idx]], array, newValue, ++idx
Related issue
Setting defaulValue on control din't not update the state.
Doing reverse works perfectly:
Set state to default value, and the control UI gets updated correctly as if defaulValue was given.
Code:
let defaultRole = "Owner";
const [role, setRole] = useState(defaultRole);
useEffect(() => {
setMsg(role);
});
const handleChange = (event) => {
setRole(event.target.value );
};
// ----
<TextField
label="Enter Role"
onChange={handleChange}
autoFocus
value={role}
/>
Define a state for your default value
Surround your input with a div and a key prop
Set the key value to the same value as the defaultValue of the input.
Call your setDefaultValue defined at the step 1 somewhere to re-render your component
Example:
const [defaultValue, setDefaultValue] = useState(initialValue);
useEffect(() => {
setDefaultValue(initialValue);
}, false)
return (
<div key={defaultValue}>
<input defaultValue={defaultValue} />
</div>
)
Give value to parameter "placeHolder".
For example :-
<input
type="text"
placeHolder="Search product name."
style={{border:'1px solid #c5c5c5', padding:font*0.005,cursor:'text'}}
value={this.state.productSearchText}
onChange={this.handleChangeProductSearchText}
/>
Use value instead of defaultValue and change the value of the input with the onChange method.

Resources