The title is the best I can really phrase it, however, heres my Microsoft Paint version of what I'm trying to do. If you guys know how to do it feel free to reply.
Heres what it is doing
Heres what I want it to do
Sorry for the bad drawings not the most artistic.
I'm not sure whats the use case you are trying to solve, but I will assume that it is something similar to how search could work. ie: You enter something in input fields, push submit button -> hide form you are currently in and show results in next page.
Pseudo code could look like this
class Search extends React.Component {
state = {
query: "",
results: null
}
handleOnChange = e => {
this.setState({query: e.target.value})
}
formSubmit = () => {
//do your request to api
.then(response => this.setState({results: response}))
}
render() {
return(
this.state.results === null ? (
<form>
<input type="text" value={this.state.query} onChange={this.handleOnChange}/>
<button type="submit" onClick={this.formSubmit}>Submit</button>
</form>
):(
<div>{this.state.results}</div>
)
)
}
}
React basically is a single page application so one way in doing what you want is to use react-router-dom when the submit button is clicked, you send the form data to the backend and then dispatch a new route that will display the component that has the API data. inside the componentDidMount life cycle of the component that will display the api data you make a fetch to get the data.
Related
Fixed it!
How did I fix it?
Well, after looking for every documentation and question around fetching, I remembered that a button with a function with parenthesis always fires. The moment I removed the parenthesis it stopped the infinite loop of GET requests.
<button onclick={fetchingId}>Chercher le produit</button>
Now, the issue is that it doesn't seem to work and the function doesn't fire.
I am currently trying a few Gutenberg components to help me fire that function and fetch the API on demand.
Edit
Someone made me notice that I should pass the function and not its result.
The function inside the onclick should be:
() => fetchingId(attributes.ids)
I switched the HTML button with a Component names <Button>:
<Button
isPrimary
className='fetch-product'
onClick={() => fetchingId(attributes.ids)}>
Chercher un produit
</Button>
From the WordPress StackExchange somebody told me that we shouldn't use functions inside a React component and use useEffect instead.
The other thread with a solution.
The original post:
I've been building a Gutenberg Block that sends a GET request to the Woocommerce REST API.
It is composed of a text input that receives an ID of a product and fetches it to the Woocommerce REST API. It also has a button that fires a function to fetch the product with the API.
The issue with the GET Requests
The fetching works well, but it keeps sending multiple GET requests, even when I do not click the button to fire the function. Simply clicking the input sends multiple requests when i only need one everytime I change the ID.
The code
This is the first part of the edit function:
const edit = ({ attributes, setAttributes }) => {
const blockProps = useBlockProps();
// Wrapped the WooCommerce.get() function in a function named `fetchingId` to call it with a button
const fetchingId = async (id) => {
// The WooCoommerce.get() function
const response = await WooCommerce.get(`products/${id}`)
.then((response) => {
console.log(response.data);
setAttributes({ price: response.data.price });
setAttributes({ name: response.data.name });
})
.catch((error) => {
console.log(error.response.data);
setAttributes({ price: '' });
setAttributes({ name: '' });
});
}
...
}
This is another part of the function: an input that updates the Product ID that is used for the GET request and a button linked to the fetchingId() function.
return <div {...blockProps}>
<div class="wrapper-input" >
<TextControl
label={__('ID du Produit', 'ingredient-unique')}
value={attributes.id}
onChange={(val) => setAttributes({ id: val })}
className='control-id'
/>
</div>
<button onclick={fetchingId(attributes.id)}>Chercher le produit</button>
...
</div>;
Fixed it!
How did I fix it?
Well, after looking for every documentation and question around fetching, I remembered that a button with a function with parenthesis always fires. The moment I removed the parenthesis it stopped the infinite loop of GET requests.
<button onclick={fetchingId}>Chercher le produit</button>
Now, the issue is that it doesn't seem to work and the function doesn't fire. I am currently trying a few Gutenberg components to help me fire that function and fetch the API on demand.
Edit
Someone made me notice that I should pass the function and not its result.
The function inside the onclick should be:
() => fetchingId(attributes.ids)
I switched the HTML button with a Component names :
<Button
isPrimary
className='fetch-product'
onClick={() => fetchingId(attributes.ids)}>
Chercher un produit
</Button>
From the WordPress StackExchange somebody told me that we shouldn't use functions inside a React component and use useEffect instead.
I’m pretty new to this so apologies in advance if I'm being dumb. I’m building a react application on top of the WordPress rest API. I’m trying to do something pretty basic which is to create a component showing a list of pages, each with a link which takes the user to a new view showing the individual ‘page’ with all the data for that page.
I’m almost there but am having problems outputting the correct data on the individual pages.
The approach I’ve taken is to take the id from match.params and then match it up with the page id passed down through props using javascript ‘find’.
This kind of works. I can console log the data for the individual page out from inside the ‘getPage’ method in the PageSingle component if I call it in the render method but the moment I try to access any individual values such as the id I get the old Cannot read property 'id' of undefined.
Probably not very clearly explained so please see code below:
PageList.js
import React from 'react';
import { Link } from 'react-router-dom';
const PageList = (props) => {
const pages = props.pages.map( (page) => {
return (
<div key={page.id}>
<Link to={`/pagelist/${page.id}`}>{page.title.rendered}</Link>
</div>
);
});
return <div className="page-list">{pages}</div>
};
export default PageList;
PageSingle.js
import React from 'react';
class PageSingle extends React.Component {
getPage = (props) => {
let thePage = props.pages.find(page => page.id === Number(props.match.params.pageId) );
**console.log(thePage); // 1. this works
console.log(thePage.id); // 2. this leads to error**
return thePage;
};
render() {
this.getPage(this.props);
return (
<h4>PageSingle</h4>
)
}
};
export default PageSingle;
JSON shown in console when it works – I’ve removed some so as not to take up too much space but you get the idea
{
content: {rendered: "Test Page 2 Text. Test Page 2 Text. Test Page 2 Text. Test Page 2 Text. Test Page 2 Text. ", protected: false}
date: "2019-09-30T13:38:47"
excerpt: {rendered: "Test Page 2 Text. Test Page 2 Text. Test Page 2 Text. Test Page 2 Text. Test Page 2 Text.", protected: false}
id: 14
link: "http://localhost/all_projects/wordpress/sites_main/my_projects/portfolio/wordpress/test-page-2/"
slug: "test-page-2"
status: "publish"
title: {rendered: "Test Page 2"}
type: "page"
__proto__: Object
}
The props are sent to page single using Browser Router. The routes themselves are defined in the App.js component and look like this. Not sure if this is relevant or not, probably not.:
Routes
<Route
path="/pagelist" exact
render={ (props) => <PageList {...props} pages={ this.state.pages } /> }
/>
<Route exact
path="/pagelist/:pageId"
render={(props) => <PageSingle {...props} pages={ this.state.pages } /> }
/>
Obviously, the end goal is to eventually display the relevant data via the render method but I actually need to access that data before I can do that.
It’s probably something really basic that I’m just not understanding.
Any pointers in the right direction would be much appreciated.
Thanks.
If you have the correct data in your console.log then I think this is because console.log executes after a small delay,
Try doing this and see if you get the property
setTimeout(function()
{
console.log(thePage.id)
}, 100);
usually keys are added after the console.log call
Hope it helps
Okay, for anyone interested, the answer lay in the fact that an array with an object inside of it was being returned in PageSingle.js.
The answer therefore lay in grabbin gthe correct page with an if statement and pushing the required values to an array and then accessing that with bracket notation:
let thisPage = [];
props.pages.map((page) => {
if (page.id === Number(props.match.params.pageId)) {
thisPage.push(page.id, page.title.rendered, page.content.rendered, page.featured_images);
}
return thisPage;
})
return (
<div>
<h4>{thisPage[1]}</h4>
<p>{thisPage[2]}</p>
</div>
)
Got to be a less convoluted way of doing this though so, any suggestions please let me know.
I am trying to create a comment section for a site im working on. Once the comment form (inside AfterCommentButtonClick) is submitted, the state formSubmitted changes from false to true which triggers a conditional statement inside the render method. This calls a child component which receives the users comment and does some styling with it. The issue im having is that, i want my app to allow more than one comment. Is there a way to save the previously rendered comment, and then create a new instance of <UserComment> as currently, following form submittal the old one is simply overwritten. I also need to reset the textInput state following the submittal of the form, to reset the form for the next comment. However, again im not sure how to do this without entering setState inside render, which will cause an infinite loop
import React from 'react'
import UserComment from './UserComment'
class CommentSection extends React.Component {
constructor(props){
super(props)
this.state = {selectedFile: this.props.selectedFile, textinput : '', formSubmitted:false}
}
onFormSubmit (event){
event.preventDefault()
this.setState({formSubmitted:true})
}
render(){
//conditional render depending on if comment button has been clicked or not. props.selectedFile only
//passed here from parent if user clicks comment button
const file = this.props.selectedFile
let messageToUser
if (file !=null){
messageToUser = <AfterCommentButtonClick
selectedFile = {file}
onTextChange = {(e)=> this.setState({textinput: e.target.value})}
onFormSubmit = {(e)=>this.onFormSubmit(e)}
/>
}else {
messageToUser = <BeforeCommentButtonClick />
}
return (
<div>
<div> {messageToUser}</div>
<div className="ui container comments">
{this.state.formSubmitted &&
<UserComment commentText = {this.state.textinput}/>
/*conditionally send text typed into comment bar if user submits form*/
}
</div>
</div>
)
}
}
Create a functional component to render all of your submitted comments. To do this, you would keep an array of 'submitted comments' in state and, on submission of a new comment, just add the new user comment to the array of submitted comments. Pass that submitted comments array from state to your new functional component. Use the array.map() function to render the array of submitted components by rendering a <UserComment/> for each item in the array.
So, on submission of a User Comment, it would just add to the submitted comments component, the UI re-renders and updates with the new UserComment in your submitted comments. This should be entirely separate logic.
i.e. Render method of your <CommentsSection/> component would look something like this:
render() {
return (<div>
{this.props.submittedComments.map((comment) => (
<UserComment author={comment.author} content={comment.content}></UserComment>))}
</div>);
}
You can add another state field to store the comments in an array. So, when you get a new comment, you do this:
this.setState({
comments: [...this.state.comments, newComment]
});
And in your render method, you map over that array and display a single comment component for every comment in this.state.comments
this.state.comments.map(comment => <UserComment commentText = {comment}}/>);
I am currently working on a weather app constructed with react-create-app that works on React Router to show data for 5 different days. Each day (presented in a box) is a Link that redirects the second component to it specifically so that it can show more detailed data for the specific day.
I have a function that generates the data for each tile:
generateTileData() {
const weatherData = this.state.data;
if (!weatherData) return null;
let days = [];
const newData = [...weatherData].filter(day => {
let dateFromAPI = moment.unix(day.dt).date();
if (days.indexOf(dateFromAPI) > -1) {
return false;
} else {
days.push(dateFromAPI);
return true;
}
});
// console.log(days)
return newData.map((day, item) => {
const dateId = day.dt;
return (
<Link to={`/w/${dateId}`}>
<WeatherTile key={day.dt} index={item} {...day} date={day.dt_txt} />
</Link>
);
});
}
That then is being rendered:
<HashRouter>
<React.Fragment>
<div className="columns is-gapless tiles">
{this.generateTileData()}
</div>
{weatherData && <Route exact path="/w/:dateId" render={({ match }) => <Weather data={weatherData} day={[...weatherData].find(day => day.dt == match.params.dateId)} />} />}
</React.Fragment>
</HashRouter>
The problem is that as I am fetching the data (through a form) only after the user inputs it, when I accidentally refresh the page, the application does not know where the data comes from and thus gives me a nice error that it cannot read the property main of undefined (as in my Weather component:
const Weather = ({ day }) => {
console.log(day);
const data = day;
return (
<div className="info">
<p>{data.main.temp}</p>
<p>{data.main.humidity}</p>
</div>
)
}
Is there any way that I can either prevent the user from refreshing or by default redirecting the user on refresh to the main page (that would be path="/")?
There are a few ways I think you could go about getting around this issue. One way would be how #Pat Mellon described and to add a refresh state and redirect should it be true or false (however you want to build the logic).
You could also add in some default logic, which is more of a 'React' way of doing things. For instance, if there is no user info being seen by the app, simply output some example weather.
Up next would to be adding in url logic and sticking your user data into the url itself. Kind of like an API call, the app would look at the url and not the state to pull information to display. (Look at URI.js for a nice way of doing this)
Another way that might be a little more UX friendly is to persist the data from the form into the user's local storage. Using this method would allow you to maintain the data and use it even if the page is refreshed, closed, computer is restarted, etc. The only caveat is remembering to clear the user's local storage (and more importantly, the information that you saved and not that of other websites) if the user is to ever re-enter information into your form.
Lot's of fun ways to untangle this issue, and I'm sure plenty more than the four I've listed.
I'm using Reactjs. I have a form that will populate my database onSubmit with just a name property. Assuming inserting data is success, How do I jump to back to my landing page in the promise? my landing page url is a simple '/'. or should i jump back to the landing page somewhere else and not in the promise.
const React = require('react')
const axios = require('axios')
class AddNewRecordLabel extends React.Component {
constructor (props) {
super(props)
this.state = ({
artists: []
})
this.onSubmit = this.onSubmit.bind(this)
}
componentDidMount () {
axios.get('http://localhost:5050/recordLabels')
.then((res) => {
this.setState({
artists: res.data
})
})
}
onSubmit (e) {
e.preventDefault()
if (!this.refs.name.value) {
console.log('fill in the name input')
} else {
var object = {
name: this.refs.name.value
}
axios.post('http://localhost:5050/recordLabels', object)
.then((res) => {
//change here
})
}
}
render () {
return (
<div>
<h3>add new record label</h3>
<form onSubmit={this.onSubmit}>
<label>
<input type='text' ref='name' placeholder='name'/>
</label>
<br></br>
<button type='submit'> Add Record Label </button>
</form>
</div>
)
}
}
module.exports = AddNewRecordLabel
Typically, you would create a library using a flux pattern which uses dispatchers, actions and stores to control your components. If you want to save a lot of time, there are libraries using the flux pattern out there such as react-redux and react-flux.
I have used a home grown flux pattern before. I'm using redux now which is fairly easy to use as well as pretty quick to develop with from my personal experience. There's great documentation on it and a lot of support from the community.
If you want to keep it simple, you might want to rethink your strategy such as returning message that either replaces the form giving them options such as going back to the home page or even leaves the form so they have an opportunity to add another record label. You would also want to check to see if there was an error and show some sort of message stating why it was unsuccessful based on the response. If you want to go back to the home page you could simply add this to your promise...
window.location.href = '/'
...but ideally, you would want to move your service calls to another file and return responses, then act on those responses in your component accordingly, but the general recommended approach is to do it by dispatchers and listeners and update your state or props within this component.