I just want to preface this that I am learning JavaScript and React so this is all very new to me.
I am building a "simple" movie rating app and need to be able to push a review to a div "on submit" and cannot figure out how to do so. I have tried using update state in react and/or creating functions to try to accomplish this and cannot figure out how to do this for the life of me. I did somewhat succeed using the latter method, but was getting errors about using unique key props. The other problem was I am to use a star-rating component and when I submitted the review, it wasn't pushing that to the div. This is where I'm at currently:
import { Button, Form, Input } from "reactstrap";
import Stars from "./stars";
export default function ReviewForm() {
const [reviews, setReviews] = useState("");
const onChange = (e: any) => {
setReviews(e.target.value);
};
const onSubmit = (e: any) => {
console.log("Form Submitted");
};
return (
<div className="form-container">
<Stars />
<Form onSubmit={onSubmit}>
<Input
className="form-control" type="text"
placeholder="Enter you review"
value={reviews}
onChange={onChange}
/>
<br></br>
<Button type="submit" className="btn btn-primary">
Submit
</Button>
</Form>
</div>
);
}
// This is what I have in my Stars component:
import React, { useState } from "react";
import { FaStar} from 'react-icons/fa'
const Stars = () => {
const [rating, setRating] = useState(0);
const [hover, setHover] = useState(null);
return(
<div>
{[...Array(5)].map((star, i) => {
const ratingValue = i + 1;
return <label>
<input
type="radio"
name="rating"
value={ratingValue}
onClick={() => setRating(ratingValue)}
/>
<FaStar
className="star"
color={ratingValue <= (hover || rating) ? "gold" : "lightgray"}
size={20}
onMouseEnter={() => setHover(ratingValue)}
onMouseLeave={() => setHover(null)}
/>
</label>;
})}
<p>I rate this movie {rating + " stars"}</p>
</div>
);
};
export default Stars```
Here is the working version of your code. You should use key in your map and e.preventDefault() in your form submit function. As final touch you should set another state inside your form submit and show this value in a div or some html element. Also I see that you want to get child state into parent so you can call callback for this https://codesandbox.io/embed/brave-euler-ybp9cx?fontsize=14&hidenavigation=1&theme=dark
ReviewForm.js
export default function ReviewForm() {
const [reviews, setReviews] = useState("");
const [value, setValue] = useState("");
const [star, setStar] = useState();
const onChange = (e: any) => {
setReviews(e.target.value);
};
const onSubmit = (e: any) => {
e.preventDefault();
setValue(reviews + " with " + star + " star ");
};
return (
<div className="form-container">
<Stars setStar={setStar} />
<Form onSubmit={onSubmit}>
<Input
className="form-control"
type="text"
placeholder="Enter you review"
value={reviews}
onChange={onChange}
/>
<br></br>
<Button type="submit" className="btn btn-primary">
Submit
</Button>
<div>{value}</div>
</Form>
</div>
);
}
Stars.js
const Stars = ({ setStar }) => {
const [rating, setRating] = useState(0);
const [hover, setHover] = useState(null);
const handleClick = (ratingValue) => {
setRating(ratingValue);
setStar(ratingValue);
};
return (
<div>
{[...Array(5)].map((star, i) => {
const ratingValue = i + 1;
return (
<label key={i}>
<input
type="radio"
name="rating"
value={ratingValue}
onClick={() => handleClick(ratingValue)}
/>
<FaStar
className="star"
color={ratingValue <= (hover || rating) ? "gold" : "lightgray"}
size={20}
onMouseEnter={() => setHover(ratingValue)}
onMouseLeave={() => setHover(null)}
/>
</label>
);
})}
<p>I rate this movie {rating + " stars"}</p>
</div>
);
};
export default Stars;
You probably are seeing a page refresh when you press the submit button. This is the default behavior of HTML forms.
When using React or any front-end framework, you'd want to handle the form submission yourself rather than letting the browser submit your forms.
In your onSubmit function, add the following line
e.preventDefult()
const onSubmit = (e: any) => {
e.preventDefault()
console.log("Form Submitted");
};
Your code will work perfectly.
import { Button, Form, Input } from "reactstrap";
import Stars from "./stars";
export default function ReviewForm() {
const [Reviews, setReviews] = useState("");
const [ReviewsRating, setReviewsRating] = useState(5);
const [Reviews_, setReviews_] = useState([]);
const onChange = (e: any) => {
setReviews(e.target.value);
};
const onSubmit = (e: any) => {
e.preventDefault()
console.log("Form Submitted");
//After upload to the server
setReviews_([Reviews, ...Reviews_]
};
return (
<div className="form-container">
<Stars getRating={getRating}/>
<Form onSubmit={onSubmit}>
<Input
className="form-control" type="text"
placeholder="Enter you review"
value={reviews}
onChange={onChange}
/>
<br></br>
<Button type="submit" className="btn btn-primary">
Submit
</Button>
</Form>
<div class="reviews">
{Reviews_.map(item => <div> {item}</div> )}
</>
</div>
);
}```
Then to get the stars rating value use props like...
And make sure you call that property (function) inside your Starts component
const getRating =(value)=>{
setReviewsRating(value)
}
Related
I want to create a button on my site that will allow the user to upload a file from their device (computer, phone).
I have already made this button and it opens a window where you can select a file.
But I ran into a problem: I would like to display the name of this file on the page.
I have this component highlighted in orange:
export default function BoxForChoiceFile() {
return (
<Card sx={styles.CommonStyle}>
<SelectFileButton></SelectFileButton>
</Card>
);
}
And in the BoxForChoiceFile component there is a button for choosing a file
export default function SelectFileButton() {
const fileInput = useRef();
const selectFile = () => {
fileInput.current.click();
}
return (
<div>
<input type="file" style={{ "display": "none" }} ref={fileInput} />
<Button onClick={selectFile} className='btn btn-primary' sx={styles.DispositionBottom}>
<span>Upload</span>
</Button>
</div>
)
}
In addition to what Alberto Anderick Jr answered, this is how you can pass the file name from the child component to the father component and show it below SelectFileComponent, in your BoxForChoiceFile :
export default function BoxForChoiceFile() {
const [fileName, setFileName] = useState("");
return (
<div>
<Card sx={styles.CommonStyle}>
<SelectFileButton setFileName={setFileName} />
</Card>
<h5>{fileName}</h5>
</div>
);
}
and in your SelectFileComponent:
export default function SelectFileButton(props) {
const {setFileName} = props;
const [file, setFile] = useState(null);
const fileInput = useRef();
const selectFile = () => {
fileInput.current.click();
};
const updateName = () => {
setFile(fileInput.current.files[0]);
setFileName(fileInput.current.files[0]?.name);
}
return (
<div>
<input type="file" style={{ display: "none" }} ref={fileInput} onChange={updateName} />
<button onClick={selectFile} className="btn btn-primary">
<span>Upload</span>
</button>
</div>
);
}
This is an example of how you can capture the name of the file selected into a state:
import { useRef, useState } from "react";
export default function SelectFileButton() {
const [fileName, setFileName] = useState("");
const fileInput = useRef();
const selectFile = () => {
fileInput.current.click();
};
const updateName = () => {
setFileName(fileInput.current.files[0]?.name);
}
return (
<div>
<input type="file" style={{ display: "none" }} ref={fileInput} onChange={updateName} />
<button onClick={selectFile} className="btn btn-primary">
<span>Upload</span>
</button>
<h5>{fileName}</h5>
</div>
);
}
This code can definitely be improved depending on your case, but it should give you what you want.
I want to scroll to the bottom of the div whenever a new message is sent in the Message. I can do this with jQuery but wondering what the best approach in React is.
This is the Messages.js component I am in. Thank you in advance!
const Messages = (props) => {
const [inputMessage, setInputMessage] = useState('');
const handleChange = (event) => {
setInputMessage(event.target.value);
}
const handleSubmit = (event, message) => {
event.preventDefault();
props.setRoomMessages([...props.roomMessages, {id: Date.now(), name: props.username, message: inputMessage}]);
setInputMessage('');
}
const messages = props.roomMessages.map((message, index) => {
return (
<Message
key={index}
name={message?.name}
message={message?.message}
/>
)
})
return (
<>
<div className="Messages">
{messages}
</div>
<form className="chat-input form-inline" onSubmit={handleSubmit}>
<div className="form-group">
<input
className="form-control"
type="text"
value={inputMessage}
placeholder="Type a message..."
onChange={handleChange}
/>
<button className="btn btn-link">Send</button>
</div>
</form>
</>
)
}
export default Messages;
Try this, one way to do it is to use ref
import { useState, useRef } from 'react';
const Messages = props => {
const [inputMessage, setInputMessage] = useState('');
const myMessage = useRef(null);
const handleChange = event => {
setInputMessage(event.target.value);
};
const handleSubmit = (event, message) => {
event.preventDefault();
props.setRoomMessages([...props.roomMessages, { id: Date.now(), name: props.username, message: inputMessage }]);
setInputMessage('');
// HERE IS THE NEW CODE
if (myMessage && myMessage.current) {
myMessage.current.scrollTop = myMessage.current.scrollHeight;
}
};
const messages = props.roomMessages.map((message, index) => {
return <Message key={index} name={message?.name} message={message?.message} />;
});
return (
<>
<div className="Messages" ref={myMessage}>
{messages}
</div>
<form className="chat-input form-inline" onSubmit={handleSubmit}>
<div className="form-group">
<input
className="form-control"
type="text"
value={inputMessage}
placeholder="Type a message..."
onChange={handleChange}
/>
<button className="btn btn-link">Send</button>
</div>
</form>
</>
);
};
export default Messages;
At this line. I added an onChange Attribute to change the textbox state and update it. But it is not working, I have no idea why because I try the same/ similar thing at other project and it is working correctly. Is there a limit on how many hooks I can use? Is it better to setState as an object with multiple parameters instead of multiple hooks?
<input value={textbox} onChange={e => setTextBox(e.target.value)}></input>
Below is the full code
import React, { useState } from 'react'
export const StartPage = () => {
const [players, SetPlayers] = useState(["jaw", "weihan"])
const [view, setView] = useState(selection)
//const [roles, SetRoles] = useState(null)
const [textbox, setTextBox] = useState("")
const selection = (
<div>
<button onClick={() => setView(addPlayer)}> Create Game </button>
<button> Join Game </button>
</div>
)
const addPlayer = (
<div className="add-player">
<form>
<input value={textbox} onChange={e => setTextBox(e.target.value)}></input>
<button type="submit" > Add more</button>
</form>
<ul>
{players.map(item => <li>{item}</li>)}
</ul>
</div>
)
return (
<section id="start-page">
{view}
</section>
)
}
onChange={e =>{
e.preventDefault()
setTextBox(e.target.value)
}}
Is this you want to achieve?
const [players, SetPlayers] = useState(['jaw', 'weihan']);
const [textbox, setTextBox] = useState('');
const [showPlayersForm, setShowPlayersForm] = useState(false);
const handleSubmit = e => {
e.preventDefault();
SetPlayers(prevState => [...prevState, textbox]);
setTextBox('');
};
const addPlayer = (
<div className='add-player'>
<form onSubmit={handleSubmit}>
<input value={textbox} onChange={e => setTextBox(e.target.value)} />
<button type='submit'> Add more</button>
</form>
<ul>
// use key when you loop trough items
{players.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
</div>
);
return (
<div>
<button onClick={() => setShowPlayersForm(true)}>Create game</button>
<button> Join Game </button>
{showPlayersForm && addPlayer}
</div>
);
Here is my Login component:
const Login = () => {
const [user, setUser] = useState("");
const [pass, setPass] = useState("");
return (
<div>
<p>Login</p>
<input
type="text"
onChange={(e) => {
setUser(e.target.value);
}}
/>
<input
type="password"
onChange={(e) => {
setPass(e.target.value);
}}
/>
<button onClick={submit(user, pass)}>
Submit
</button>
</div>
);
};
It renders on my webpage, but it calls the submit() function whenever I input to these two: text and password. Looking at my code, I've only set the onClick to call the submit function.
Is there something wrong with my code?
EDIT: Removed classNames for easier viewing
You are calling the submit function on every render. onClick takes a function, but you are directly calling a function.
<button onClick={submit(user, pass)}>
Submit
</button>
will be replaced by
<button onClick={()=>submit(user, pass)}>
Submit
</button>
try :
const Login = () => {
const [user, setUser] = useState("");
const [pass, setPass] = useState("");
const onSubmit = () => {
submit(user,pass)
}
return (
<div>
<p>Login</p>
<input
type="text"
onChange={(e) => {
setUser(e.target.value);
}}
/>
<input
type="password"
onChange={(e) => {
setPass(e.target.value);
}}
/>
<button onClick={onSubmit}>
Submit
</button>
</div>
);
};
I have a React Form app with name and description fields.
The form data is held in a local state object using Hooks:
const [data,setData] = useState({name: '', description: ''}).
The <Form /> element creates inputs and passes their value using <Field initialValue ={data.name} />
Within the <Field /> element, this initialValue is passed to the state, which controls the input value (updated onChange):
const [value,setValue] = useState(initialValue).
But if I reset the data object (see handleResetClick function), the inputs don't clear (even though the data object clears). What am I doing wrong? I thought that changing the data would cause a re-render and re-pass initialValue, resetting the input.
Codepen example here - when I type in the inputs, the data object updates, but when I click Clear, the inputs don't empty.
function Form() {
const [data, setData] = React.useState({name: '', description: ''});
React.useEffect(() => {
console.log(data);
},[data]);
const onSubmit = (e) => {
// not relevant to example
e.preventDefault();
return;
}
const handleResetClick = () => {
console.log('reset click');
setData({name: '', description: ''})
}
const onChange = (name, value) => {
const tmpData = data;
tmpData[name] = value;
setData({
...tmpData
});
}
return (
<form onSubmit={onSubmit}>
<Field onChange={onChange} initialValue={data.name} name="name" label="Name" />
<Field onChange={onChange} initialValue={data.description} name="description" label="Description" />
<button type="submit" className="button is-link">Submit</button>
<button onClick={handleResetClick} className="button is-link is-light">Clear</button>
</form>
)
}
function Field(props) {
const {name, label, initialValue, onChange} = props;
const [value, setValue] = React.useState(initialValue);
return (
<div>
<div className="field">
<label className="label">{label}</label>
<div className="control">
<input
name={name}
className="input"
type="text"
value={value}
onChange={e => {
setValue(e.target.value)
onChange(name, e.target.value)
}}
/>
</div>
</div>
</div>
)
}
class App extends React.Component {
render() {
return (
<div className="container">
<Form />
</div>
);
}
}
ReactDOM.render(
<App />,
document.getElementById('app')
)
On handleResetClick you change the data state of Form, but it doesn't affect its children.
Try adding a listener for initialValue change with useEffect:
function Field(props) {
const { name, label, initialValue, onChange } = props;
const [value, setValue] = React.useState(initialValue);
useEffect(() => {
setValue(initialValue);
}, [initialValue]);
return ...
}
You may be better off having Field as a controlled component (ie it's state is managed by the parent component rather than maintaining its own state). In this example I've swapped in value instead of initialValue and simply passed that down as props to the field. onChange then calls the parent method and updates the state there (which is automatically passed back down to the field when it renders):
const { useState, useEffect } = React;
function Form() {
const [data, setData] = React.useState({
name: '',
description: ''
});
useEffect(() => {
console.log(data);
}, [data]);
const onSubmit = (e) => {
e.preventDefault();
return;
}
const handleResetClick = () => {
setData({name: '', description: ''})
}
const onChange = (e) => {
const { target: { name, value } } = e;
setData(data => ({ ...data, [name]: value }));
}
return (
<form onSubmit={onSubmit}>
<Field onChange={onChange} value={data.name} name="name" label="Name" />
<Field onChange={onChange} value={data.description} name="description" label="Description" />
<button type="submit" className="button is-link">Submit</button>
<button onClick={handleResetClick} className="button is-link is-light">Clear</button>
</form>
)
}
function Field(props) {
const {name, label, value, onChange} = props;
return (
<div>
<div className="field">
<label className="label">{label}</label>
<div className="control">
<input
name={name}
className="input"
type="text"
value={value}
onChange={onChange}
/>
</div>
</div>
</div>
)
}
function App() {
return (
<div className="container">
<Form />
</div>
);
}
ReactDOM.render(
<App />,
document.getElementById('root')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>