Dropdown select component state one step behind - reactjs

I have no clue why the selected dropdown value is one step behind in the URL search params string. My url is like this: http://localhost/?dropdownsel=. Below is my code:
//App.js
//update params value
function setParams({ dropdownsel }) {
const searchParams = new URLSearchParams();
searchParams.set("dropdownsel", dropdownsel);
return searchParams.toString();
}
class App extends Component {
state = {
dropdownsel: ""
};
//update url params
updateURL = () => {
const url = setParams({
dropdownsel: this.state.dropdownsel
});
//do not forget the "?" !
this.props.history.push(`?${url}`);
};
onDropdownChange = dropdownsel => {
this.setState({ dropdwonsel: dropdownsel });
this.updateURL();
};
render() {
return (
<Dropdownsel
onChange={this.onDropdownselChange}
value={this.state.dropdownsel}
/>
);
}
}
Below is dropdownsel component code:
//Dropdownsel.js
const attrData = [{ id: 1, value: AA }, { id: 2, value: BB }];
class Dropdownsel extends Component {
onDropdownChange = event => {
this.props.onChange(event.target.value);
};
render() {
return (
<div>
<select value={this.props.value} onChange={this.onDropdownChange}>
<option value="">Select</option>
{attrData.map(item => (
<option key={item.id} value={item.value}>
{" "}
{item.name}
</option>
))}
</select>
</div>
);
}
}
export default Dropdownsel;

Thanks for formatting my code. I don't know how to do it every time when I post question. I figured it out myself. I need to make a call back function for updateURL() because the setState() is not executed immediately. so my code should be revised like below:
onDropdownChange = (dropdownsel) => {
this.setState({ dropdwonsel:dropdownsel }, ()=>{this.updateURL();
});
};

The problem occurs because this.setState is asynchronous (like a Promise or setTimeout are)
So there are two workarounds for your specific case.
Workaround #1 - using a callback
Use the callback option of this.setState.
When you take a look at the declaration of setState, it accepts an "optional" callback method, which is called when the state has been updated.
setState(updater[, callback])
What it means is that, within the callback, you have an access to the updated state, which was called asynchronously behind the scene by React.
So if you call this.updateURL() within the callback, this.state.dropdownsel value will be the one you are expecting.
Instead of,
this.setState({ dropdwonsel: dropdownsel });
this.updateURL();
Call this.updateURL in the callback.
// Note: '{ dropdwonsel }' is equal to '{ dropdwonsel: dropdwonsel }'
// If your value is same as the state, you can simplify this way
this.setState({ dropdwonsel }, () => {
this.updateURL()
});
Workaround #2 - passing the new value directly
You can also pass the new value directly as an argument of this.updateURL() (which might make testing easier as it makes you method depend on a value, which you can fake).
this.setState({ dropdwonsel });
this.updateURL(dropdownsel );
Now your this.updateURL doesn't depend on the this.state.dropdownsel, thus you can can use the arg to push the history.
//update url params
updateURL = dropdownsel => {
const url = setParams({
dropdownsel
});
//do not forget the "?" !
this.props.history.push(`?${url}`);
};

Related

How to prevent unnecessary re-renders with React Hooks, function components and function depending on item list

List of items to render
Given a list of items (coming from the server):
const itemsFromServer = {
"1": {
id: "1",
value: "test"
},
"2": {
id: "2",
value: "another row"
}
};
Function component for each item
We want to render each item, but only when necessary and something changes:
const Item = React.memo(function Item({ id, value, onChange, onSave }) {
console.log("render", id);
return (
<li>
<input
value={value}
onChange={event => onChange(id, event.target.value)}
/>
<button onClick={() => onSave(id)}>Save</button>
</li>
);
});
ItemList function component with a handleSave function that needs to be memoized.
And there is a possibility to save each individual item:
function ItemList() {
const [items, setItems] = useState(itemsFromServer);
const handleChange = useCallback(
function handleChange(id, value) {
setItems(currentItems => {
return {
...currentItems,
[id]: {
...currentItems[id],
value
}
};
});
},
[setItems]
);
async function handleSave(id) {
const item = items[id];
if (item.value.length < 5) {
alert("Incorrect length.");
return;
}
await save(item);
alert("Save done :)");
}
return (
<ul>
{Object.values(items).map(item => (
<Item
key={item.id}
id={item.id}
value={item.value}
onChange={handleChange}
onSave={handleSave}
/>
))}
</ul>
);
}
How to prevent unnecessary re-renders of each Item when only one item changes?
Currently on each render a new handleSave function is created. When using useCallback the items object is included in the dependency list.
Possible solutions
Pass value as parameter to handleSave, thus removing the items object from the dependency list of handleSave. In this example that would be a decent solution, but for multiple reasons it's not preferred in the real life scenario (eg. lots more parameters etc.).
Use a separate component ItemWrapper where the handleSave function can be memoized.
function ItemWrapper({ item, onChange, onSave }) {
const memoizedOnSave = useCallback(onSave, [item]);
return (
<Item
id={item.id}
value={item.value}
onChange={onChange}
onSave={memoizedOnSave}
/>
);
}
With the useRef() hook, on each change to items write it to the ref and read items from the ref inside the handleSave function.
Keep a variable idToSave in the state. Set this on save. Then trigger the save function with useEffect(() => { /* save */ }, [idToSave]). "Reactively".
Question
All of the solutions above seem not ideal to me. Are there any other ways to prevent creating a new handleSave function on each render for each Item, thus preventing unnecessary re-renders? If not, is there a preferred way to do this?
CodeSandbox: https://codesandbox.io/s/wonderful-tesla-9wcph?file=/src/App.js
The first question I'd like to ask : is it really a problem to re-render ?
You are right that react will re-call every render for every function you have here, but your DOM should not change that much it might not be a big deal.
If you have heavy calculation while rendering Item, then you can memoize the heavy calculations.
If you really want to optimize this code, I see different solutions here:
Simplest solution : change the ItemList to a class component, this way handleSave will be an instance method.
Use an external form library that should work fine: you have powerfull form libraries in final-form, formik or react-hook-form
Another external library : you can try recoiljs that has been build for this specific use-case
Wow this was fun! Hooks are very different then classes. I got it to work by changing your Item component.
const Item = React.memo(
function Item({ id, value, onChange, onSave }) {
console.log("render", id);
return (
<li>
<input
value={value}
onChange={event => onChange(id, event.target.value)}
/>
<button onClick={() => onSave(id)}>Save</button>
</li>
);
},
(prevProps, nextProps) => {
// console.log("PrevProps", prevProps);
// console.log("NextProps", nextProps);
return prevProps.value === nextProps.value;
}
);
By adding the second parameter to React.memo it only updates when the value prop changes. The docs here explain that this is the equivalent of shouldComponentUpdate in classes.
I am not an expert at Hooks so anyone who can confirm or deny my logic, please chime in and let me know but I think that the reason this needs to be done is because the two functions declared in the body of the ItemList component (handleChange and handleSave) are in fact changing on each render. So when the map is happening, it passes in new instances each time for handleChange and handleSave. The Item component detects them as changes and causes a render. By passing the second parameter you can control what the Item component is testing and only check for the value prop being different and ignore the onChange and onSave.
There might be a better Hooks way to do this but I am not sure how. I updated the code sample so you can see it working.
https://codesandbox.io/s/keen-roentgen-5f25f?file=/src/App.js
I've gained some new insights (thanks Dan), and I think I prefer something like this below. Sure it might look a bit complicated for such a simple hello world example, but for real world examples it might be a good fit.
Main changes:
Use a reducer + dispatch for keeping state. Not required, but to make it complete. Then we don't need useCallback for the onChange handler.
Pass down dispatch via context. Not required, but to make it complete. Otherwise just pass down dispatch.
Use an ItemWrapper (or Container) component. Adds an additional component to the tree, but provides value as the structure grows. It also reflects the situation we have: each item has a save functionality that requires the entire item. But the Item component itself does not. ItemWrapper might be seen as something like a save() provider in this scenario ItemWithSave.
To reflect a more real world scenario there is now also a "item is saving" state and the other id that's only used in the save() function.
The final code (also see: https://codesandbox.io/s/autumn-shape-k66wy?file=/src/App.js).
Intial state, items from server
const itemsFromServer = {
"1": {
id: "1",
otherIdForSavingOnly: "1-1",
value: "test",
isSaving: false
},
"2": {
id: "2",
otherIdForSavingOnly: "2-2",
value: "another row",
isSaving: false
}
};
A reducer to manage state
function reducer(currentItems, action) {
switch (action.type) {
case "SET_VALUE":
return {
...currentItems,
[action.id]: {
...currentItems[action.id],
value: action.value
}
};
case "START_SAVE":
return {
...currentItems,
[action.id]: {
...currentItems[action.id],
isSaving: true
}
};
case "STOP_SAVE":
return {
...currentItems,
[action.id]: {
...currentItems[action.id],
isSaving: false
}
};
default:
throw new Error();
}
}
Our ItemList to render all items from the server
export default function ItemList() {
const [items, dispatch] = useReducer(reducer, itemsFromServer);
return (
<ItemListDispatch.Provider value={dispatch}>
<ul>
{Object.values(items).map(item => (
<ItemWrapper key={item.id} item={item} />
))}
</ul>
</ItemListDispatch.Provider>
);
}
The main solution ItemWrapper or ItemWithSave
function ItemWrapper({ item }) {
const dispatch = useContext(ItemListDispatch);
const handleSave = useCallback(
// Could be extracted entirely
async function save() {
if (item.value.length < 5) {
alert("Incorrect length.");
return;
}
dispatch({ type: "START_SAVE", id: item.id });
// Save to API
// eg. this will use otherId that's not necessary for the Item component
await new Promise(resolve => setTimeout(resolve, 1000));
dispatch({ type: "STOP_SAVE", id: item.id });
},
[item, dispatch]
);
return (
<Item
id={item.id}
value={item.value}
isSaving={item.isSaving}
onSave={handleSave}
/>
);
}
Our Item
const Item = React.memo(function Item({ id, value, isSaving, onSave }) {
const dispatch = useContext(ItemListDispatch);
console.log("render", id);
if (isSaving) {
return <li>Saving...</li>;
}
function onChange(event) {
dispatch({ type: "SET_VALUE", id, value: event.target.value });
}
return (
<li>
<input value={value} onChange={onChange} />
<button onClick={onSave}>Save</button>
</li>
);
});

Showing data by React

I'm going to to click in div and show a text in another div in multiple items.
I have got series of data that contains some objects in one array(json file) and it will be shown by react.The code will be done up tohandelrule = ((e,element,i) =>{....}) . There is an onClick function ({e => this.handelrule(e,element,i)}) for each item.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
OtherRooms: {},
divVisibles: {},
loadingVisible: {},
resultRule: {},
};
}
render() {
const { data } = this.state;
const renderHotel = data.map((item, i) => {
return <div class="item">
<div class="moreInfo" onClick={(e) => this.showDiv(e, item, i)}><span>show more data</span></div>
<div key={i} className={`${!this.state.loadingVisible[i] ? "unvisible" : "visible"}`}>
<div id="ballsWaveG">
</div>
</div>
<div id="box-info" key={i} className={` ${!this.state.divVisibles[i] ? "unvisible" : "visible"}`}>
<div class="table">
{this.state.OtherRooms[i]}
</div>
</div>
</div>
});
return (
<div>
{renderHotel}
</div>
);
}
showDiv = (e, element, i) => {
this.showLoading(e, element, i);
setTimeout(() => {
fetch('/json.bc', {
method: 'POST'
})
.then(response => response.text())
.then(text => {
var Maindata = JSON.parse(text.replace(/\'/g, '"'))
this.setState(prevState => ({
Details: {
...prevState.Details,
[i]: this.renderDetails(Maindata, i),
},
divVisibles: { ...prevState.divVisibles, [i]: !prevState.divVisibles[i] },
loadingVisible: { ...prevState.loadingVisible, [i]: "" }
}))
}).catch(error => console.error(error))
}, 1000);
}
renderDetails(element, i) {
var indents = [];
indents.push(<div>
<span>{this.renderRule(element, i)}</span>
<div key={i} className={`${!this.state.loadingVisible[i] ? "unvisible" : "visible"}`}>
<div id="ballsWaveG">
</div>
</div>
<div key={i}>{this.state.resultRule[i]}</div>
</div>
)
return (
indents
)
}
showLoading = (e, elem, val) => {
this.setState(prevState => ({
loadingVisible: { ...prevState.loadingVisible, [val]: !prevState.loadingVisible[val] }
}))
};
renderRule(element, i) {
return <span class="txtRul" onClick={e => this.handelruleRoom(e, element, i)}>Show Rule</span>
}
handelruleRoom = (e, element, i) => {
var mainprovider = element.id.provider
if (mainprovider == undefined) {
return ''
} else {
this.showLoading(e, element, i);
/////the loading whould not be shown //////
setTimeout(() => {
var return_rule = function () { ////This part will be done but the result will not be shown in class="resultRule" ///////
var tmp = null;
$.ajax({
'async': false,
'type': "POST",
'global': false,
'dataType': 'html',
'url': "rule.bc",
'data': { 'mainprovider': JSON.stringify(mainprovider), },
'success': (response) => {
tmp = response;
}
});
return tmp;
}();
return this.setState(prevState => ({
resultRule: { ...prevState.resultRule, [i]: return_rule }, ///In this part return_rule does not set in resultRule ////
loadingVisible: { ...prevState.loadingVisible, [i]: "" }
}))
}, 1000);
}
}
}
ReactDOM.render(<App />, document.getElementById('Result'));
Actually there is a problem with this part this.setState( prevState => ({....})
There are quite a few issues with your code. I'm going to do my best to point them out and show alternatives, not merely to be critical of you, but to help you learn!
You're using quite a few state values. Think about the problem you're trying to solve, and determine the minimum possible states you need to accomplish that. For example, some of the conditional rendering your currently handling in state, can be offloaded to the render method itself using a ternary operator to render a loading component if a state value is undefined
render() {
<div>
{
!this.state.value
? <Loading />
: this.state.value
}
</div>
}
You're using class="name" in some elements. Because in Javascript, class is a keyword, when setting class names on JSX elements, use the className="name" property instead.
You're nesting your fetch calls inside of setTimeouts. This is not a very reliable method of ensuring the fetch returns a value, as network calls should be treated as taking an arbitrary amount of time. If it takes more than 1 second to fetch those files (for whatever reason) then your whole app will break. You have a few alternatives: You can add a series of callbacks, chain together .then()s, or use the new async/await ES6 feature. I'm going to give you an example of how you could utilize async/await for much cleaner and more reliable code:
showDiv = async (event, element, index) => {
this.showLoading(event, element, index);
let response = await fetch("/json.bc", { method: "POST" });
await response.text();
let data = JSON.parse(text.replace(/\'/g, '\"'));
this.setState(prevState => ({
Details: {
...prevState.Details,
[index]: this.renderDetails(data, index)
},
divVisible: {
...prevState.divVisibles,
[index]: !prevState.divVisibles[index]
},
loadingVisible: {
...prevState.loadingVisible,
[index]: ""
}
}));
};
Since this showDiv method itself is asynchronous, when you call it in your top-level code, you will need to chain a .then() and add additional code after it to ensure the new state has taken effect going forward
render() {
// Note that you cannot use await in this top-level code
showDiv.then(() => {
// The rest of your code that relies on the state set in showDiv
}).catch(err => console.error(err));
}
You may save yourself headache of dealing with asyncronous operations by simply importing the files containing your data directly into your app. The only reason you would need to use fetch() as you are, is if you were grabbing your data from a separate backend (like if you had a separate server for your database and REST API). Otherwise, you can pull those files into the client-side bundle with everything else:
// Use whatever the relative path is to these files
import data from "./json.bc";
import rules from "./rules.bc";
The power of React lies in its component based model. I see that your using a lot of helper functions within the same class to render different aspects of your interface. It may work for now, but it looks messy, and is confusing to debug and maintain. Instead, try extracting some of that functionality into new component classes, and then importing them into your App component to render. There will be a learning curve to figure out passing props, and changing state of a parent from a child, but you will gain the benefits of using React as it was intended. Most notably, you can avoid having to store arrays of data for every single div you're rendering. By encapsulating the functionality into a component, each component can manage it's own state and properties.
In your render() method, you placed a semicolon after a div near the end of your renderHotel function. Any JavaScript you want to place within a JSX block, must be put inside curly braces { }, and semicolons are not required to terminate JSX elements. It may have been a typo, but just in case wanted to add this in.
Using var may be more familiar to you, but it's spilling the variable scope all over the place, keeping variables that are no longer needed in memory because you're allowing them a class-wide scope. Instead, use const or let, the keep the variable context contained within their lexical scope. If you need access to one of those locally scoped variables, it can be solved through composition (the way you've organized your functions).
As to the problem you originally posted about, all of the problems I listed may be contributing; typos in JSX, class instead of className, async timing problems. Although perhaps the primary issue is regarding number 3. You are returning the value of tmp and binding it to return_rule possibly before the AJAX call has time to resolve. I would recommend refactoring your handleRuleRoom function using the async/await example I provided as a guide.
Your handelruleRoom function has no index declared inside its scope
So you just need to figure out what is index inside your handelruleRoom function which is probably the last argument of it you called it - i
Try changing index to i
like this
this.showLoading(e, DetailsRoomJ, i);
// and also here
return this.setState(prevState => ({
resultRule: { ...prevState.resultRule, [i]: return_rule },
loadingVisible: { ...prevState.loadingVisible, [i]: "" }
}))
Also if you care there are many things wrong with this snippet
class should be className
are you sure you need a sync function (because you are blocking you browser until you get your response)

How to set a state array with values from TextField using onchange

I am new to react and am trying to add string values in an array. I am using Material-UI objects.
My state has
this.state: {
roles: []
}
A button pushes an undefined element in roles, incrementing its length.
clickAddRole = () => {
this.setState({roles: this.state.roles.concat([undefined]) });
};
So now we have some length to the roles array.
The Textfield is generated with
this.state.roles.map((item, i)=> {
return (
<TextField id={'roles['+i+']'} label={'role '+i} key={i} onChange={this.handleChange('roles['+i+']')} />
)
})
the onchange event is handled as below
handleChange = name => event => {
console.log(name);
this.setState({[name]: event.target.value});
console.log(this.state.roles);
}
The console.log statements generate output like
roles[0]
[undefined]
I expect
roles[0]
["somedata"]
what is going wrong here? The data does not get set in the roles array.
The whole code file is
const styles = theme => ({
error: {
verticalAlign: 'middle'
},
textField: {
marginLeft: theme.spacing.unit,
marginRight: theme.spacing.unit,
width: 300
},
submit: {
margin: 'auto',
marginBottom: theme.spacing.unit * 2
}
})
class AddModule extends Component {
constructor() {
super();
this.state = {
roles:[],
open: false,
error: ''
}
}
clickSubmit = () => {
const module = {
roles: this.state.roles || undefined
}
create(module).then((data) => {
if (data.error) {
this.setState({error: data.error})
} else {
this.setState({error: '', 'open': true});
}
})
}
clickAddRole = () => {
this.setState({roles: this.state.roles.concat([undefined]) });
};
handleChange = name => event => {
console.log(name);
this.setState({[name]: event.target.value});
console.log(this.state.roles);
}
render() {
const {classes} = this.props;
return (
<div>
<Button onClick={this.clickAddRole} >Add Role</Button>
{
this.state.roles.map((item, i)=> {
return (
<TextField className={classes.textField} id={'roles['+i+']'} label={'role '+i} key={i} onChange={this.handleChange('roles['+i+']')} />
)
})
}
</div>
)
}
}
I think you're making the whole code a bit overcomplicated creating names for each input field. What I would do is change the handleRolesChange or handleChange (not really sure if you changed its name) method so that it takes the index instead of a name.
handleRolesChange = index => event => {
const { roles } = this.state;
const newRoles = roles.slice(0); // Create a shallow copy of the roles
newRoles[index] = event.target.value; // Set the new value
this.setState({ roles: newRoles });
}
Then change the render method to something like this:
this.state.roles.map((item, index) => (
<TextField
id={`roles[${index}]`}
label={`role ${index}`}
key={index}
onChange={this.handleRolesChange(index)}
/>
))
Guy I have the issue (maybe temporarily).
I an array-element is a child of the array. so changing the data in the array-element does not need setState.
So this is what I did....
handleRolesChange = name => event => {
const i = [name];
this.state.roles[i]=event.target.value;
}
I also change the Textfield onchange parameter to
onChange={this.handleRolesChange(i)}
where i is the index starting from zero in the map function.
All this works perfectly as I needed.
However, if you think that I have mutated the roles array by skipping setState, I will keep the Question unanswered and wait for the correct & legitimate answer.
Thanks a lot for your support guys.
We must try and find the solution for such basic issues. :)
Are you positive it's not being set? From React's docs:
setState() does not always immediately update the component. It may
batch or defer the update until later. This makes reading this.state
right after calling setState() a potential pitfall. Instead, use
componentDidUpdate or a setState callback (setState(updater,
callback)), either of which are guaranteed to fire after the update
has been applied. If you need to set the state based on the previous
state, read about the updater argument below.
Usually logging state in the same block you set the code in will print the previous state, since state has not actually updated at the time the console.log fires.
I would recommend using React Dev Tools to check state, instead of relying on console.log.

HOC/Render-Call Back or Library function?

I'm working on a project where a prospect needs to be sent an email about a property they are interested in. There is a top level component that fetches the property information and prospect's contact info from the database and passes to its children. There are two components that share the same process of formatting the information, and then call an email function that sends off an email. A sample of one component looks like this:
import sendEmail from 'actions/sendEmail'
class PropertyDetail extends React.Componet {
state = {
unit: undefined,
prospect: undefined,
};
componentDidMount = () => {
this.setState({
unit: this.props.unit,
prospect: this.props.prospect,
});
};
sendEmail = ({ id, address, prospect }) => {
// quite a bit more gets formatted and packaged up into this payload
const payload = {
id,
address,
prospectEmail: prospect.email,
};
emailFunction(payload);
};
handleEmail = () => {
sendEmail(this.state);
};
render() {
return (
<div>
<h1>{this.state.unit.address}</h1>
<p>Send prospect an email about this property</p>
<button onClick={this.handleEmail}>Send Email</button>
</div>
);
}
}
and the other component looks like this
class UpdateShowing extends React.Component {
state = {
unit: undefined,
prospect: undefined,
showingTime: undefined,
};
componentDidMount = () => {
this.setState({
unit: this.props.unit,
propsect: this.props.prospect,
showingTime: this.props.showingTime,
});
};
sendEmail = ({ id, address, prospectEmail }) => {
// quite a bit more gets formatted and packaged up into this payload
const payload = {
id,
address,
prospectEmail,
};
emailFunction(payload);
};
handleUpdate = newTime => {
// get the new date for the showing ...
this.setState({
showingTime: newTime,
});
// call a function to update the new showing in the DB
updateShowingInDB(newTime);
sendEmail(this.state);
};
render() {
return (
<div>
<p>Modify the showing time</p>
<DatePickerComponent />
<button onClick={this.handleUpdate}>Update Showing</button>
</div>
);
}
}
So I see some shared functionality that I'd love to not have to repeat in each component. I'm still learning (working my first job), and why not use this as an opportunity to grow my skills? So I want to get better at the HOC/Render props pattern, but I'm not sure if this is the place to use one.
Should I create a component with a render prop (I'd rather use this pattern instead of a HOC)? I'm not even sure what that would look like, I've read the blogs and watched the talks, ala
<MouseMove render={(x, y) => <SomeComponent x={x} y={y} />} />
But would this pattern be applicable to my case, or would I be better off defining some lib function that handles formatting that payload for the email and then importing that function into the various components that need it?
Thanks!
I think a provider or a component using render props with branching is a better fit for you here
see this doc: https://lucasmreis.github.io/blog/simple-react-patterns/#render-props

How to set an initial API call in react ES6

So the problem is as follows: I have a search function that get's an default value passed on from another place, the search works, but only when it gets a new input, hence if I'm passing "Dress" it wont call my api function before i change something in the input.
I've tried a bit of everything like setInitialState(), but without any noteworthy success.
As you can see I'm getting a onTermChange from my Searchbar that's passed to handleTermChange which then updates my products:[], but I need this.props.location.query to be the default search term, as this is the passed on variable.
handleTermChange = (term) => {
const url = `http://localhost:3001/products?title=${term.replace(/\s/g, '+')}`;
request.get(url, (err, res) => {
this.setState({ products: res.body })
});
};
render () {
return (
<div className='col-md-12' style={{ margin: '0 auto' }}>
<div className='row searchPageHeader' style={{ padding: '10px', backgroundColor: '#1ABC9C' }}/>
<SideMenu />
<SearchBar onTermChange={this.handleTermChange}
defaultValue={this.props.location.query}/>
<ProductList products={this.state.products}
onProductSelect={selectedProduct => this.openModal(selectedProduct)}/>
<ProductModal modalIsOpen={this.state.modalIsOpen}
selectedProduct={this.state.selectedProduct}
onRequestClose={ () => this.closeModal() }/>
<Footer />
</div>
);
}
I would personally just do the same logic in componentDidMount(), like this:
componentDidMount () {
const url = `http://localhost:3001/products?title=${this.props.location.query}`;
request.get(url, (err, res) => {
this.setState({ products: res.body })
});
}
Note that since you are doing an asynchronous call products won't be populated from the API result until a moment after the component is mounted. Make sure you initialize products in initialState (I assume this returns an array, so initialize it as an empty array).
Opinion: Since you are following the event handler naming conventions (i.e onX followed by handleX) I would avoid calling handleTermChange() inside componentDidMount() because the function name suggests it's bound to an event listener. So calling it directly is just bad practice in my opinion. So if you'd rather call a function in here, rather than writing out the logic like I did above, I would do the following:
componentDidMount() {
this.changeTerm(this.props.location.query);
}
changeTerm = (term) => {
const url = `http://localhost:3001/products?title=${term.replace(/\s/g, '+')}`;
request.get(url, (err, res) => {
this.setState({ products: res.body })
});
};
handleTermChange = (term) => {
this.changeTerm(term);
}
Your render() remains unchanged. Maybe a stretch, but I prefer it this way.

Resources