I'm new to svelte and I'm trying to get a sense for best practices. I want to create a dynamic form component I can use throughout an app. Each instance of the form will have the same styling - but each form will be tied to different input values in the store, will have different submit logic, etc.
Here's a simple example (that doesn't work):
App.svelte
<script>
import store from "./store";
import Form from "./Form.svelte";
const { inputValueA, inputValueB } = store;
const handleSubmitA = () => {
alert($inputValueA);
};
const handleSubmitB = () => {
alert($inputValueB);
};
</script>
<div>
<Form
inputValue={$inputValueA}
labelText="form input a"
handleSubmit={handleSubmitA}
/>
<Form
inputValue={$inputValueB}
labelText="form input b"
handleSubmit={handleSubmitB}
/>
</div>
Form.svelte
<script>
export let handleSubmit = () => null;
export let inputValue = "";
export let labelText = "";
</script>
<form>
<label for="input">{labelText}</label>
<input id="input" type="text" value={inputValue} />
<button on:click|preventDefault={handleSubmit}>submit</button>
</form>
This example doesn't work, I don't believe the inputValue prop is bound to the text input correctly. Any folks out there with svelte experience willing to share how you'd approach this?
Here's a sandbox:
https://codesandbox.io/s/svelte-form-component-ujnwki
Let me know what you suggest! Thanks.
Not really sure what is exactly is your question but here is the fix for your example to make it work:
you have to bind the value to your component to be able to change it form the child
bind:inputValue={$inputValueA}
and bind the value to the input itself to make its value reactive
bind:value={inputValue}
I don't really have much experience with React but I was trying to work on a small project for myself.
So I have this dropdown list, which has 2 values, and I'm trying to console.log the current state of camera which should hold the values for the list option that you click on.
So let's say I click on an option for the 1st time, I get a log of an empty value (the initial state of my camera), then I click on the other option and I get logged the option that I clicked the 1st time. From what I understand this might be due to the fact that setState is async ?
Even so, what should I do to fix my problem, so that when I click for the first time on an option it logs the actual option ? I'm thinking of using useEffect someway but I can't really figure out how to use it for handling events.
This is my code:
import React, { useState, useEffect } from 'react'
import { Link } from 'react-router-dom';
import style from './Join.module.css';
const Join = () => {
const [nume, setNume] = useState('')
const [camera, setCamera] = useState('')
return (
<div className={style.mainJoinContainer}>
<div className={style.secondaryJoinContainer}>
<h1 className={style.header}>
Conecteaza-te!
<div>
<input placeholder="Nume" className={style.joinInput} type="text" onChange={(event) => { setNume(event.target.value); console.log(nume) }} />
</div>
<div className={style.margin}>
<label>Camera</label>
<select name="Camere" placeholder="Camere" onChange={(event) => { setCamera(event.target.value); console.log(camera) }}>
<option value="Camere" style={{ display: "none" }}>Camere</option>
<option value="Gluma">Gluma</option>
<option value="Joke">Jokes</option>
</select>
</div>
<Link to="/interaction/chat">
<button className={`${style.joinButton} ${style.margin}`} type="submit">Logheaza-te</button>
</Link>
</h1>
</div>
</div >
)
}
export default Join
I will appreciate any sort of guidance for this problem. I've tried looking up some similar situation and tried useEffect or async-await for some reason, but I can't really figure it out myself. Thank you for your time!
You can use useEffect to "watch" a state variable.
useEffect(() => {
// do stuff
console.log(camera);
}, [camera]);
How to clear the value inside the input in function Admin after I click the "Add" button? Should i use another class based component instead of a functional component?
I have set the value of one of the input box as : value={props.item} and in the this.setState I update the value of item as item:"".
AddInfo(info){
let s = this.state.products;
let obj ={name:""};
obj.name=info.productName;
s.push(obj);
this.setState({
products:s,
item:"" //Here i set the value of item equal to an empty string.
})
console.log(this.state.products);
}
function Admin(props){
let productName="";
return (
<div>
<input type="text" required placeholder="Product Name" onChange={(e)=>{productName=e.target.value}} value={props.item}></input><br/>
<button type="Submit" onClick{(e)=>props.AddInfo({productName})}>Add</button>
</div>
)
}
You have to save your input within a local state of the input function:
AddInfo(info){
let s = this.state.products;
let obj ={name:""};
obj.name=info.productName;
s.push(obj);
this.setState({
products:s,
})
console.log(this.state.products);
}
function Admin(props){
const [productName, setProductName] = useState('');
return (
<div>
<input type="text" required placeholder="Product Name" onChange={(e)=> setProductName(e.target.value) value={productName}></input><br/>
<button type="Submit" onClick{(e)=> {props.AddInfo({productName}); setProductName('')}}>Add</button>
</div>
)
}
This will work for you, since you are not mutating the productName variable anymore but now you are saving it in a local state of that input function.
Hope this helps!
Admin is like a form, and the main decision you have to make is rather you want it to be controlled (info is stored in stated, and state is reflected in the ui), or uncontrolled (info is taken from the dom once 'Add' is clicked.
Since you want to empty the input once 'Add' is clicked it makes sense to make this component controlled.
The next decision is rather you want it to be a functional component, or a class component. In nowadays it doesn't really matter (functional components can now use state with the state hook).
To store state in you functional component use React's useState hook.
function Admin({addInfo}){
const [productName, setProductName] = useState(")
return (
<div>
<input
type="text"
placeholder="Product Name"
onChange={(e)=>{
setProductName(e.target.value)
}
value={prodcutName}>
</input>
<button
onClick{(e)=>{
props.addInfo({productName})
setProductName("") // Will set the input to an empty string
}
>
Add
</button>
</div>
)
}
Fails to run first if condition.
So once component is loaded it shows "Student Progress related Stats" and when once topic-link is set(in some other component which is removed too properly) it shows second display. Everything works fine till now but when topic-link is removed if fails to run first if condition and shows second display still. Basically I want to change my component view based on topic-link is there or not.
import React, { Component } from "react";
import {connect} from 'react-redux';
import {getQuestionsList} from '../../store/actions/questionActions';
class Test extends Component {
render(){
let display;
let topicLink = localStorage.getItem('topic-link');
if(!topicLink){
display =
<div style={{textAlign:'center',
fontSize:'22px'}}>
<p>Student Progress related Stats</p>
</div>
}
else if(topicLink){
display =
this.props.questions.map(question => (
<div key={question.id} style={{border:'1px solid #000',marginBottom:'15px'}}>
<div dangerouslySetInnerHTML={{__html: question.direction}} />
<div dangerouslySetInnerHTML={{__html: question.question}} />
<div>
<form>
<input type="radio" name="option" value="(A)"/>{question.option_a}<br/>
<input type="radio" name="option" value="(B)"/>{question.option_b}<br/>
<input type="radio" name="option" value="(C)"/>{question.option_c}<br/>
<input type="radio" name="option" value="(D)"/>{question.option_d}
</form>
</div>
</div>
))
}
return (
<div>
{display}
</div>
);
}
};
const mapStateToProps = state => {
return {
questions: state.questions.items,
}
}
export default connect(mapStateToProps, {getQuestionsList})(Test);
Here where i set the localStorage. On Every onTopicClick I get the desired output(meaning second if statement runs and it updates the component). Only when localStorage is removed it stays on the second if statement which not be the case(it should run first if statement). I have two fucntions(in seperate components) like onSectionClick and onTopicClick like this:
onTopicClick = () => {
this.props.getQuestionsList(this.props.topicId);
let topicName = this.props.name;
topicName = topicName.replace(/\s+/g, '-').toLowerCase();
let topicLink = "/updates/daily-practice-questions/" + this.props.sectionName + "/" + topicName;
this.props.history.push(topicLink);
localStorage.setItem('topic-link', topicLink);
}
onSectionClick = () => {
this.setState(prevState => ({
isOpened: !prevState.isOpened
}));
let sectionName = this.props.name;
sectionName = sectionName.replace(/\s+/g, '-').toLowerCase();
this.setState({sectionName: sectionName});
this.props.history.push("/updates/daily-practice-questions/" + sectionName);
if(this.state.isOpened){
this.props.history.push("/updates/daily-practice-questions");
localStorage.removeItem('topic-link')
}
}
One of the hardest topics in data communication probably.
let topicLink = localStorage.getItem('topic-link'); This line is the money line. Basically, you already identified the driver of this component, for instance, topicLink.
The next thing is to make sure this component use this variable as a prop input. I noticed you are using redux already, therefore this variable could be part of the state variables there.
Next step will be to see if your store can update this variable from localStorage. This probably is another topic, for instance, check the localStorage every 5 seconds, and then update the store variable.
However, it's not a good idea to sync components via a localStorage variable, instead, you should bootstrap your code and save this localStorage variable as a state variable first and store in your redux for example.
So either way, input prop is the first step to go, it'll help you test the code as well.
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.