redux form keeps not setting initial values multi form - javascript

ok I'll admit this is a bit of a hot mess but please bear with me.
Trying to understand why my redux-forms:
A: don't set ititialValues until I have set the store elsewhere in the app.. for eg. if I have another event that calls, getFirstTrip, then its in the store, and can load my form correctly with intialValues.
B: wipe themselves out when I click the router link again. Every time I click the router Link it behaves the same as the initial click.. even though the store is there.. the fields and initialValues are empty.
Using:
react-router, react-redux, react-form
structure like this:
view.js
const onSubmit = (formValues) => {
props.updateTrip(formValues); //< traditional put action to api
};
useEffect(() => {
props.getFutureTrip(); // traditional get action to api
//the action dispatches the reducer to create the vacations.nextVacation.tripInfos
}, []);
return (
<div>
Register Page
<TripRegistrationForm onSubmit={onSubmit} />
<Highlight>
{output}</Highlight>
</div>
);
};
const mapStateToProps = state => {
return {
initialValues: state.vacations.nextVacation.tripInfos //< this doesn't seem necessary.
}
}
export default connect(mapStateToProps,{updateTrip, getFutureTrip}) (TripsRegistration);
Parent form TripRegistrationForm.js
class TripRegistrationForm extends Component {
constructor(props) {
console.log("props", props);
super(props);
}
render() {
const { onSubmit, onChange} = this.props;
return (
<div>
<childForm
onChange={onChange}
onSubmit={onSubmit}
/>
</div>
);
}
}
TripRegistrationForm.propTypes = {
onSubmit: PropTypes.func.isRequired,
};
TripRegistrationForm = reduxForm({ form: "tripRegistration" })(TripRegistrationForm);
TripRegistrationForm = connect((state, ownProps) => ({
initialValues: state.vacations.nextVacation.tripInfos
}))(TripRegistrationForm);
export default TripRegistrationForm;
the form contents (these do populate initial values.. but not consistently... ever)
childForm.js
const RegistrationThirdPage = (props) => {
const { handleSubmit, pristine, submitting, lockForm } = props;
return (
<form disabled={lockForm} className="ui form error" >
<FormSection >
<h2>
Trip & Travel Details
</h2>
<Form.Group>
<Field
name="arriving"
placeholder="Arriving"
component={renderDatePicker}
label="Select your arrival date"
/>
<Field
component={renderSelect}
label="Number of Days"
name="packageDays"
options={singleArrayToKVs(colors)}
/>
</Form.Group>
<Form.Group>
<div>
<Button type="button" disabled={lockForm} className="ui-button green" onClick={handleSubmit(values => props.onSubmit({...values, lockVacation:false}))}>
Save my changes
</Button>
</div><div>
<Button type="submit" className="ui-button primary" disabled={bookVacation(pristine,submitting,lockForm)}
onClick={handleSubmit(values => props.onSubmit({...values, lockVacation:true}))}>
Book my vacation
</Button>
</div>
</Form.Group>
</FormSection>
</form>
);
};
export default reduxForm({
form: "tripRegistration", //Form name is same
destroyOnUnmount: false,
forceUnregisterOnUnmount: true, // <------ unregister fields on unmount
// validate,
})(RegistrationThirdPage);
my actions look like this:
export const getFutureTrip = () => async (dispatch,getState) => {
const{token} = getState().auth
if(!token)
return null;
const response = await axios.get(`/api/trip/futureTrip`,{headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
}});
console.log("response.data from getFutureTrip", response.data);
dispatch({ type: GET_FUTURE_TRIP, payload: response.data });
};

Related

React JS (Form Update Not Working) - How to set state immediately after getting data from API using mapDispatchToProps?

I am very new to react and working on some basics where I came up in the situation - I want to set state immediately after API call.
Scenario:
2 Forms:
1st form => accepts id and calls api to get data of single user
2nd form => updates data
PROBLEM: I want to set state when I get data after clicking submit button on 1st Form
import React, { Component, useEffect } from 'react'
import { connect } from 'react-redux';
import { getSingleUser } from '../redux/user/userActions';
export class UsersContainerUpdate extends Component {
constructor(props) {
super(props);
console.log(props.propFirstName);
this.state = {
id: '',
// first_name: props.propFirstName === '' ? '' : props.propFirstName,
first_name: props.propFirstName,
last_name: props.propLastName === '' ? '' : props.propLastName,
phone: props.propPhone === '' ? '' : props.propPhone,
email: props.propEmail === '' ? '' : props.propEmail,
address: props.propAddress === '' ? '' : props.propAddress,
city: props.propCity === '' ? '' : props.propCity,
state: props.propState === '' ? '' : props.propState,
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.handleUpdate = this.handleUpdate.bind(this);
}
handleChange = (field, event) => {
this.setState({ [field]: event.target.value });
}
handleSubmit(event) {
// alert('A name was submitted: ' + this.state.name);
event.preventDefault();
const {
id
} = this.state;
const postData = {
id: id
};
// console.log(this.state);
// console.log(postData);
this.props.getSingleUserData(id);
// if (this.props.getSingleUserData(id)) {
// this.setState({
// ...this.state,
// first_name: this.props.propFirstName
// });
// }
}
handleUpdate(event) {
// alert('A name was submitted: ' + this.state.name);
event.preventDefault();
const {
first_name,
last_name,
phone,
email,
address,
city,
state
} = this.state;
const postData = {
first_name: first_name,
last_name: last_name,
phone: phone,
email: email,
address: address,
city: city,
state: state
};
console.log(this.state);
console.log("POSTDATA:", postData);
// alert('hi');
// this.props.updateUserData(id,postData);
}
render() {
return (
<div>
<h1>Update User By ID</h1>
<form onSubmit={this.handleSubmit}>
<div>
<label>ID:</label>
<input
type="text"
value={this.state.id}
onChange={(event, newValue) => this.handleChange('id', event)}
/>
</div>
<div>
<input type="submit" value="Submit" />
</div>
</form>
<div>
<h1>Update User</h1>
<form onSubmit={this.handleUpdate}>
<div>
<label>First Name:</label>
<input
type="text"
value={this.state.first_name || this.props.propFirstName}
onChange={(event, newValue) => this.handleChange('first_name', event)}
/>
</div>
<div>
<label>Last Name:</label>
<input
type="text"
value={this.state.last_name || this.props.propLastName}
onChange={(event, newValue) => this.handleChange('last_name', event)} />
</div>
<div>
<label>Phone:</label>
<input
type="text"
value={this.state.phone || this.props.propPhone}
onChange={(event, newValue) => this.handleChange('phone', event)} />
</div>
<div>
<label>Email:</label>
<input
type="text"
value={this.state.email || this.props.propEmail}
onChange={(event, newValue) => this.handleChange('email', event)} />
</div>
<div>
<input type="submit" value="Submit" />
</div>
</form>
<div>
Notice Message : {this.props.propFirstName}
</div>
</div>
</div>
)
}
}
const mapStateToProps = state => {
console.log(state.user);
return {
propFirstName: state.user.first_name,
propLastName: state.user.last_name,
propPhone: state.user.phone,
propEmail: state.user.email,
propAddress: state.user.address,
propCity: state.user.city,
propState: state.user.state
}
}
const mapDispatchToProps = dispatch => {
return {
getSingleUserData: id => dispatch(getSingleUser(id)),
// updateUserData: (id,postData) => dispatch(updateUser(id,postData))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(UsersContainerUpdate)
The console outputs are
The console output of line:81 is the current state which is currently empty. I want to set it there.
Thanks in advance. Cheers!!
If your requirement is just to state after API call inside this.props.getSingleUserData(id),
Approach 1: (Unclean)
Add one more argument to getSingleUserData(id, setState) and pass it this.setState as an argument and inside getSingleUserData you can set the state using the function reference passed
Approach 2:
You can return a promise from getSingleUserData and do setState once it is resolves
Suggestion:
Divide your big component into individual components (like one for getting user ID and one for User data updation). The more we identify and split our project into meanigfull individual components we get more clean codes. Also when you choose to move towards functional components you can reduce lot of boiler plates with hooks.
Problem
state.user is used to set the initial value of your component's state. Changes to those props do not change your state after the component is created. They do change the values in your inputs because the initial value was an empty string '' so you default to showing the value from props. This is very misleading since those inputs don't reflect the current state.
I bet I could delete at least half of this code but that's besides the point. But take a moment to think about why props.propState === '' ? '' : props.propState is always exactly the same as just props.propState.
Solution
I have two key recommendations for how I would rewrite this:
Select user by id
Separate into multiple components
Store only the modifications in the state
Create a selector function selectUserById that selects a user from your Redux state by the id. I don't think it makes sense to store the current user properties as top-level properties of state.user like you have them right now. It seems like you also have a property state.user.users which is an array of all loaded users so I would use that.
const selectUserById = (state, id) => state.user.users.find(user => user.id === id);
Or better yet, store an object of users keyed by id.
const selectUserById = (state, id) => state.user.users[id];
With this approach we either have a complete user object or we have undefined. It's easy to check for undefined and not show the "Update User" form at all until we have real data. This makes more sense than using empty strings as the default.
We can access the complete user object from Redux. I would not duplicate that object in state. Instead I would use the state only for the properties that you have changed. You would start out with the state as an empty object and add properties to it as you modify their inputs. You can always combine the two together using object spreading.
const merged = {...existing, ...changes}
Can you implement these suggestions using class components and connect? Yes. But why add the extra hurdle? Some of the things in your code like this.handleChange.bind(this) are relics of the past when we had to do that because there wasn't a better way. But now we have better ways so you should use them.
Code
Interactive Demo on CodeSandbox
import "./App.css";
import React, { useEffect, useState } from "react";
import { useSelector, useDispatch } from "../store";
import { getSingleUser, updateUser } from "../store/slice";
const selectUserById = (state, id) => state.user.users[id];
const UserIdForm = ({ submitId }) => {
const [id, setId] = useState("");
const handleSubmit = (event) => {
event.preventDefault();
submitId(id);
};
return (
<form onSubmit={handleSubmit}>
<div>
<label>ID:</label>
<input
type="text"
value={id}
onChange={(event) => setId(event.target.value)}
/>
</div>
<div>
<input type="submit" value="Submit" />
</div>
</form>
);
};
const UpdateUserForm = ({ id }) => {
const [changes, setChanges] = useState > {};
const existing = useSelector((state) => selectUserById(state, id));
const dispatch = useDispatch();
// respond to changes in id by clearing the changes state and requesting the user
useEffect(() => {
dispatch(getSingleUser(id));
setChanges({});
}, [dispatch, setChanges, id]);
if (!existing) {
return <div>Loading User...</div>;
}
const merged = { ...existing, ...changes };
const handleChange = (property, event) => {
// in function components you have to copy the whole state
setChanges((prevChanges) => ({
...prevChanges,
[property]: event.target.value
}));
};
const handleUpdate = (event) => {
event.preventDefault();
const postData = { ...merged, id };
console.log("POSTDATA:", postData);
dispatch(updateUser(postData));
};
const renderInput = (property, label) => {
return (
<div>
<label>
{label}
<input
type="text"
value={merged[property]} // shows the current value or the updated value
onChange={(event) => handleChange(property, event)}
/>
</label>
</div>
);
};
return (
<form onSubmit={handleUpdate}>
{renderInput("first_name", "First Name:")}
{renderInput("last_name", "Last Name:")}
{renderInput("phone", "Phone:")}
{renderInput("email", "Email:")}
<div>
<input type="submit" value="Submit" />
</div>
</form>
);
};
const UsersContainerUpdate = () => {
// this is the id that was last submitted.
const [id, setId] = useState();
return (
<div>
<div>
<h1>Update User By ID</h1>
<UserIdForm submitId={setId} />
</div>
{!!id && ( // only load when there is an actual id
<div>
<h1>Update User</h1>
<UpdateUserForm id={id} />
</div>
)}
</div>
);
};
export default UsersContainerUpdate;

How do I sync tests in React?

I have CommentsList component which displays a list with all the comments. Each comment has a 'Reply to' button that opens the AddComment component (this component allows me to add a reply to a comment). To display the AddComment component for each comment, I used an array of states.
The AddComment component contains a text area for the input, a cancel button and a submit button. When I click on the submit button and the reply is added successfully, the AddComment component closes. If the input is empty and I click on the submit button, the component doesn't close because the input can't be empty in order to be submitted successfully.
I want to test this functionality so that I can verify if the AddComment component disappears after I post a reply.
The problem is that in tests my AddComment component doesn't disappear when I click on the submit button. What I noticed is that the comment is added succesfully, but the state of the AddComment component for the comment isn't changed. When I click on submit button the input is submitted, but the function that changes the state is never called. I think the problem might be the fact that the actions don't synchronize.
I tried to use await act to render CommentsList component to make sure that the test run closer to how React works in the browser, but my AddComment component still doesn't disappear.
Here's my CommentsList component
function CommentsList(props) {
const { t } = useTranslation();
const [hasReplyCommentBox, setHasReplyCommentBox] = useState([]);
function toggleHasReplyComment(commentIndex) {
var auxState = { ...hasReplyCommentBox };
auxState[commentIndex] = auxState[commentIndex] ? 0 : 1;
setHasReplyCommentBox(auxState);
}
function replyToCommentButton(commentIndex) {
return [
<span
id={"replyButton-" + commentIndex}
onClick={() => toggleHasReplyComment(commentIndex)}>
{t('Reply to')}
</span>
];
}
function commentReplyBox(commentIndex, parentCommentId) {
return hasReplyCommentBox[commentIndex]
?
<AddComment
id={props.codeId}
parentCommentId={parentCommentId}
commentIndex={commentIndex}
toggleHasReplyComment={toggleHasReplyComment}
updateComments={props.updateComments}
/>
:
null
}
return (
<Layout>
<Layout>
<List
itemLayout="horizontal"
dataSource={props.comments}
renderItem={(comment, commentIndex) => (
<List.Item>
<CommentCard
userId={comment.user_id}
datePosted={comment.date_posted}
body={comment.body}
actions={replyToCommentButton(commentIndex)}
children={commentReplyBox(commentIndex, comment.id)}
/>
</List.Item>
)}
/>
<AddComment
id={props.codeId}
updateComments={props.updateComments}
/>
</Layout>
</Layout>
);
}
Here's my AddComment component
function AddComment(props) {
const { t } = useTranslation();
const { TextArea } = Input;
const [form] = Form.useForm();
const [comment, setComment] = useState();
const [onCommentAddSuccess, setOnCommentAddSuccess] = useState(0);
const buttonStyle = {
float: 'right'
};
function onCommentChange(newComment) {
setComment(newComment.target.value);
}
function updateOnCommentAddSuccess(onCommentAddSuccess) {
setOnCommentAddSuccess(onCommentAddSuccess + 1);
}
function resetCommentInput() {
setComment('');
}
function onFormReset() {
form.resetFields();
}
function toggleHasReplyCommentOnPost(parentCommentId, commentIndex) {
if (parentCommentId !== undefined) {
console.log('comentariu adaugat cu succes');
props.toggleHasReplyComment(commentIndex);
}
}
function submitComment() {
let request = {
body: comment,
code_id: props.id,
parent_comment_id: props.parentCommentId
};
fetch('/api/comment/add',
{
method: 'POST',
body: JSON.stringify(request)
}
).then(response => response.json())
.then(data => {
if (data.success === 1) {
updateOnCommentAddSuccess(onCommentAddSuccess);
props.updateComments(onCommentAddSuccess);
resetCommentInput();
toggleHasReplyCommentOnPost(props.parentCommentId, props.commentIndex);
}
});
}
return (
<>
<Form form={form} name="comment" className="comment-form"
onFinish={submitComment}>
<Form.Item name="body" label={t('Comment')}>
<TextArea placeholder={t('Leave a comment')}
onChange={onCommentChange}
id={"parent-comment-" + props.parentCommentId} />
</Form.Item>
<Form.Item style={buttonStyle}>
<Space>
{props.parentCommentId
?
<Button id={"cancelAddReplyComment-" + props.parentCommentId}
type="secondary" className = "comment-form-button"
onClick={
() => props.toggleHasReplyComment(props.commentIndex)
}>
{t('Cancel')}
</Button>
:
null
}
<Button type="primary" htmlType="submit"
className = "comment-form-button"
id={"post-comment-button-" + props.parentCommentId}
onClick={onFormReset}>
{t('Post')}
</Button>
</Space>
</Form.Item>
</Form>
</>
);
}
And here's how my test looks like
test ('Toggle displaying add reply to comments', async () => {
const comments = [
{
id: 'ID-1',
user_id: 'USER-ID-1',
date_posted: '2020-01-01 01:00:00',
body: 'First comment'
}
];
await act(async () => {
Promise.resolve(render(
<CommentsList comments={comments} />, container
));
});
// Open AddComment component
const replyButton = container.querySelector("#replyButton-0");
await fireEvent.click(replyButton);
// Insert input in the text area
const userInput = container.querySelector("#parent-comment-ID-1");
await userEvent.type((userInput), 'reply');
// Submit the input
const postButton = container.querySelector("#post-comment-button-ID-1");
await fireEvent.click(postButton);
// Check if the AddComment component is closed
expect(container.querySelector("#cancelAddReplyComment-ID-1")).toBeFalsy();
});

How to select data that i want when click button (React JS)

i have this example data, when i click button show more it will show popup (using Modal reactbootstrap) and i will show more detail like ID,Name,Age,City,Number,Address,Education and many more.. how i can select and get all data in popup only when i click button 'show more'
and this my code
import React from "react";
import MUIDataTable from "mui-datatables";
import axios from "axios";
import { Modal, Button } from "react-bootstrap";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
errors: null,
isLoading: true,
};
}
get = async () => {
const option = {
url: "/api/url",
method: 'POST',
headers: {
"Access-Control-Allow-Origin": "*"
},
data: {
"data": {
"data": "........."
},
"encrypt": 0
}
};
axios(option)
.then(response => {
const tableData = response.data.data.map(post => {
const {ID,Name,Age,City,Number,Address,Education} = post;
const Popup = () => {
const [lgShow, setLgShow] = React.useState(false);
const [isOpen, setIsOpen] = React.useState(false);
const showModal = () => {
setIsOpen(true);
};
const hideModal1 = () => {
setIsOpen1(false);
};
return (
<div>
<Button onClick={() => setLgShow(true)}>Show more</Button>
<Modal
size="lg"
show={lgShow}
onHide={() => setLgShow(false)}
aria-labelledby="example-modal-sizes-title-lg"
>
<Modal.Header closeButton class="modal-dialog modal-lg">
<Modal.Title id="example-modal-sizes-title-lg">
Data {nama_loket}
</Modal.Title>
</Modal.Header>
<Modal.Body>
Detail<br/>
<div><b> ID</b></div><br />
<div>{ID}</div><br />
<div><b>Name</b></div><br />
<div >{Name}</div><br />
<div><b>Age</b></div><br />
<div>{Age}</div><br />
<div><b>City</b></div><br />
<div>{City}</div><br />
<div><b>Number</b></div><br />
<div>{Number}</div><br />
<div><b>Adress</b></div><br />
<div>{Address}</div><br />
<div><b>Educaton</b></div><br />
<div>{Education}</div><br />
</Modal.Body>
</Modal>
</div>
);
};
return [
[ID],
[Name],
[Age],
[City],
[Number],
<Popup></Popup>
];
});
this.setState({
data: tableData,
isLoading: false
});
console.log(response.data.data);
console.log(this.state.data)
})
// If we catch any errors connecting, let's update accordingly
.catch(error => {
console.log(error.response);
this.setState({ error, isLoading: false })
}
);
}
componentDidMount() {
this.get();
}
render() {
const { isLoading} = this.state;
const columns = ["ID", "Name", "Age", "City", "Phone Number",""];
const options = {
filterType: "dropdown",
responsive: "scroll",
selectableRows:false,
};
return (
<div>
{!isLoading ? (
<MUIDataTable
data={this.state.data}
columns={columns}
options={options}
/>)
: (
<p>Loading...</p>
)}
</div>
);
}
}
export default App
how i get data in my popup when i click. example i have 5 row, when i click second row, data will selected and get is only the second data in second row.. can anyone help me?
You can do this by saving the row clicked in the state and then using the state to show it on the modal, just like you are doing to show your modal.
<Button
onClick={() => {
setLgShow(true)
setSelectedPost(post)
}}
>
Show more
</Button>
The idea is to have one button for each post, but you don't need to render the Modal more than once, so render the Modal outside of the response.data.data.map and use the state saved on the selectedPost to show the data inside the Modal.
I think it's better to create the table manually using css https://www.w3schools.com/css/css_table.asp
And for the rows just use React mapping https://reactjs.org/docs/lists-and-keys.html
Then create a button inside your mapping that call a function to open the modal.
Create a new state called displayedData to store the row that you want to display. Inside your render:
{data.map((value, index) => {
<tr key={index}>
<td>{value.id}</td>
<td>{value.name}</td>
. . .
<td>{value.phone}</td>
<td> <button onClick={()=> this.OpenData(value)}>Show More</button> </td>
</tr>
})}
and for the OpenData function:
async OpenData(value){
await this.setState({displayedData : value})
this.openModal()
}
Last, just use displayedData state to display your data inside the modal.
edit:
Also, move your modal from your axios fetch, just create its own function. use your axios fetch just to update your data state

How to pass a functional component inside an event?

So, I have a form and I want the user to display the values user fills in the fields as a JSON object at the end when the user clicks the submit button.
In Form.js,
state={
group:[
type-A{col1: "",
col2:""
}
]
}
handleSubmit(event) {
event.preventDefault();
<Credentials value={JSON.stringify(this.state)}/>
}
change = e =>{
this.setState({[e.target.name]: e.target.value})
};
render(){
return(
<div class="classform">
<form >
<label>
Column1:
<br/>
<input type="text"
name="group1"
placeholder="Column1"
value={this.state.column1}
onChange={e=> this.change(e)}
//other fields
//input form fields
<button onClick={this.handleSubmit}>Submit</button>
In Credentials.js,
return (
<p>{value}</p>
)
}
export default Credentials
The above code gives me an error, in handleSubmit() in second line (<Credentials value={JSON.stringify(this.state)}/>)
When the user clicks Submit button, I want to get a JSON object for the data entered in the input fields in the form and update it if the user updates any information in the fields.
Move the component to render method. and use conditional rendering.
state = {credentials: false}
handleSubmit = event => {
event.preventDefault();
this.setState({
credentials: true // display Credentials component
});
};
render() {
return (
<div>
<button onClick={this.handleSubmit}>Submit</button>
{this.state.credentials && (
<Credentials value={JSON.stringify(this.state)} />
)}
</div>
);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.0/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.21.1/babel.min.js"></script>
<div id="root"></div>
<script type="text/babel">
const Credentials = ({ value }) => {
return <p>{value}</p>;
};
class App extends React.Component {
state = { credentials: false };
handleSubmit = event => {
event.preventDefault();
this.setState({
credentials: true // display Credentials component
});
};
change = e => {
const name = e.target.name;
const nameObj = {};
nameObj[name] = e.target.value;
this.setState({ ...nameObj });
};
render() {
return (
<div>
<input
type="text"
name="col1"
value={this.state['col1']}
onChange={e => this.change(e)}
/>
<button onClick={this.handleSubmit}>Submit</button>
{this.state.credentials && (
<Credentials value={JSON.stringify(this.state)} />
)}
</div>
);
}
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
</script>

How to clear input after form submit (React)

I have a search input I'd like to clear after the value is submitted and the search is performed with the value. In similar questions, it was suggested to set the state of the input value to '', but I think that's what I tried and it didn't do anything.
I only have a parent and child component in my app. The parent component has a method for searching jokes (searchJokes), and it is passed down as a prop with a different name to the child component in the component instance with onFormSubmit={this.searchJokes}. In the child component, when the user enters something into the search input, its event.target.value is passed with onChange={e => props.onInputChange(e.target.value)} corresponding to the onSearchChange method in the parent prop, and the value is used to update the state of searchTerm.
I added searchTerm: '' to the end of the searchJokes method, which fetches a search according to the search term, as you can see in the parent component code below.
Parent component:
class App extends Component {
constructor() {
super();
this.state = {
searchTerm: '',
jokes: [],
isFetchingJokes: false,
isSearch: false
};
this.onSearchChange = this.onSearchChange.bind(this);
this.randomizeJokes = this.randomizeJokes.bind(this);
this.searchJokes = this.searchJokes.bind(this);
}
randomizeJokes() {
this.setState({
isFetchingJokes: true,
isSearch: false
});
fetch(
'https://icanhazdadjoke.com/',
{
method: 'GET',
headers: {
Accept: 'application/json'
}
})
.then(response => response.json())
.then(json => {
let joke = json.joke;
this.setState({
joke,
isFetchingJokes: false
});
});
}
searchJokes(limit = 15) {
// If nothing entered, user gets "Please fill out this field" message due to "required" attribute on input element
if (this.state.searchTerm !== '') {
this.setState({
isFetchingJokes: true,
isSearch: true
});
fetch(
`https://icanhazdadjoke.com/search?term=${
this.state.searchTerm
}&limit=${limit}`,
{
method: 'GET',
headers: {
Accept: 'application/json'
}
})
.then(response => response.json())
.then(json => {
let jokes = json.results;
this.setState({
jokes,
isFetchingJokes: false,
searchTerm: '' // <-- DOESN'T CLEAR INPUT
});
});
}
}
onSearchChange(value) {
this.setState({ searchTerm: value });
}
jokeRender() {
return (
<div>
{this.state.isSearch ?
<ul>{this.state.jokes.map(item => <li key={item.id}>{item.joke}</li>)}
</ul> : <p className="random-joke">{this.state.joke}</p>}
</div>
);
}
render() {
return (
<div>
<h1>Dad Jokes</h1>
<RetrievalForm
onFormSubmit={this.searchJokes}
onInputChange={this.onSearchChange}
isSearching={this.state.isFetchingJokes}
onRandomize={this.randomizeJokes}
/>
{this.state.isFetchingJokes ? <p className="searching-message">Searching for jokes...</p> : this.jokeRender()}
</div>
);
};
}
Child component:
const RetrievalForm = props => {
const onSubmit = e => {
// Prevents GET request/page refresh on submit
e.preventDefault();
props.onFormSubmit();
};
return (
<>
<form onSubmit={onSubmit}>
<input
type="text"
placeholder="Enter search term..."
onChange={e => props.onInputChange(e.target.value)}
required
/>
<div>
{/* Specifying type here since it's good practice; different browsers may use default types for buttons */}
<button type="submit" disabled={props.isSearching}>Search</button>
{/* type="button" stops input validation message from being displayed (on Firefox) when randomize button is clicked without anything entered */}
<button type="button" onClick={props.onRandomize} disabled={props.isSearching} className="randomize-button">
Randomize
</button>
</div>
</form>
</>
);
};
Any help would be greatly appreciated.
You need to pass your searchTerm down to the RetrievalForm and in that input set value={searchTerm} so that it's value will be bound to that state.
Basically, you need to store the input value in the component's state. When onSubmit is called, we should revert that value to an empty string.
Example with some React Hooks goodness:
import React, { Component, useState } from 'react';
const RetrievalForm = props => {
const [searchTerm, setSearchTerm] = useState('');
const onChange = e => {
const { value } = e.target;
props.onInputChange(value);
setSearchTerm(value)
}
const onSubmit = e => {
// Prevents GET request/page refresh on submit
e.preventDefault();
props.onFormSubmit();
setSearchTerm('');
};
return (
<>
<form onSubmit={onSubmit}>
<input
type="text"
value={searchTerm}
placeholder="Enter search term..."
onChange={onChange}
required
/>
<div>
{/* Specifying type here since it's good practice; different browsers may use default types for buttons */}
<button type="submit" disabled={props.isSearching}>
Search
</button>
{/* type="button" stops input validation message from being displayed (on Firefox) when randomize button is clicked without anything entered */}
<button type="button" onClick={props.onRandomize} disabled={props.isSearching} className="randomize-button">
Randomize
</button>
</div>
</form>
</>
);
};
Example here: https://stackblitz.com/edit/react-db5ire

Categories