setInterval with updated data in React+Redux - reactjs

I have setInterval setup to be working properly inside componentDidMount but the parameters are not updated. For example, the text parameter is the same value as when the component initially mounted, despite being changed in the UI. I've confirmed text's value is correctly updated in Redux store but not being passed to this.retrieveData(text). I suspect the const { text } = this.props set the value in componentDidMount, which forbids it from updating despite it being different. How would I go about this issue?
Code below is an example, but my real use-case is retrieving data based on search criteria. Once the user changes those criteria, it will update with the new result. However, I'm unable to pass those new criteria into componentDidMount so the page would refresh automatically every few seconds.
class App extends React.Component {
componentDidMount() {
const { text } = this.props //Redux store prop
setInterval(() => this.retrieveData(text), 3000)
}
retrieveData = (text) => {
let res = axios.post('/search', { text })
updateResults(res.data) //Redux action
}
render() {
const { text, results } = this.props
return (
<input text onChange={(e) => updateText(e.target.value)} />
<div>
{results.map((item) => <p>{item}</p>}
</div>
)
}
}

Because you are using componentDidMount and setTimeout methods your retrieveData is called only once with initial value of the text. If you would like to do it in your current way please use componentDidUpdate method which will be called each time the props or state has changed. You can find more information about lifecycle here https://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/.
If you would like to use setInterval just like in the question, you just need to access props inside of retrieveData method instead of using an argument.
retrieveData = () => {
let res = post("/search", { text: this.props.text });
updateResults(res); //Redux action
};
You can find working example for both cases here https://codesandbox.io/s/charming-blackburn-khiim?file=/src/index.js
The best solution for async calls would be to use some kind of middleware like https://github.com/reduxjs/redux-thunk or https://redux-saga.js.org/.
You have also small issue with input, it should be:
<input type="text" value={text} onChange={(e) => updateText(e.target.value)} />

Related

Updating one State with two fields?

I am trying to update my state data based on the users input in two fields and I'm not sure if Im going about it the right way.
The parent component Encounter.js holds the state I will try and limit the amount of code I add here so my issue is clear. So in ComponentDidUpdate I set the state with an object and create an update function to update the state. I pass the two values inside my state to another component PatientInfo along with the update state function:
componentDidUpdate(prevProps) {
if (this.props.details && this.props.details.medicalIntake && !prevProps.details.medicalIntake) {
this.setState({ pertinentMedications: {
covid19Protocol: this.props.details.medicalIntake.pertinentMedications.covid19Protocol,
note: "" || this.props.details.medicalIntake.pertinentMedications.notes
}})
}
}
pertinentMedicationsChange = (newValues) => {
this.props.setIdleTime();
this.props.setState({pertinentMedications: newValues});
}
return (
<PatientInfo
covid19Protocol={this.state.pertinentMedications.covid19Protocol}
pertinentMedicationsNote={this.state.pertinentMedications.note}
pertinentMedicationsChange={this.pertinentMedicationsChange}
/>
)
PatientInfo.js simply passes the props down.
<PertinentMedications
covid19Protocol={this.props.covid19Protocol}
pertinentMedicationsNote={this.props.pertinentMdicationsNote}
pertinentMedicationsChange={this.props.pertinentMedicationsChange}
/>
PertinentMedications.js is where the user input will be collected:
const PertinentMedications = ({
covid19Protocol,
pertinentMedicationsNote,
pertinentMedicationsChange
}) => {
const [isChecked, setIsChecked] = useState(covid19Protocol)
const onClick = (field, value) => {
setIsChecked(!isChecked)
pertinentMedicationsChange( {[field]: value})
}
const onNoteChange = (field, value) => {
pertinentMedicationsChange( {[field]: value})
}
return(
<ContentBlock title="Pertinent Medications and Supplements">
<CheckToggle onChange={() => onClick("covid19Protocol", !covid19Protocol)} checked={isChecked}>
<p>Patient has been receiving the standard supportive care and supplements as per COVID-19 protocol.</p>
</CheckToggle>
<Input
type="textarea"
name="pertinentMedications"
onChange={e => onNoteChange("notes" ,e.target.value)}
value={pertinentMedicationsNote}
/>
</ContentBlock>
)
}
export default PertinentMedications;
My true question lies within the pertinentMedicationsChange function as Im not sure how to take the data im getting from the PertinentMedications component and format it to be placed in the state. First Im not sure if I can update the state the way im trying to with these two independent fields that send their data to this function to change the state? And If it is possible Im not sure how to properly setup the key value pairs when i call setState. Can anyone help?
it seems that you are calling this.props.setState instead of this.setState. Second, this.setState also accepts a function which first param is the previous state. In this way you can use it to prevent its key values saved from pertinentMedications to be overwritten. fwiw, it's better to be consistent, not mixing hooks with react component based.
pertinentMedicationsChange = (newValues) => {
this.props.setIdleTime();
this.setState((state) => ({
// you create a new object with previous values, while newValues updates the proper keys, but not removing other keys
pertinentMedications: { ...state.pertinentMedications,...newValues}
});
)};

setState asynchronous while uploading image

I need to upload a file to the server for that i have given fileChangedHandler function for onClick event. but in that function setState is skipping because of asychronous operation.
i am giving my code below.
class MultiSelectField extends React.Component {
constructor(props) {
super(props);
this.state = { //image: '',
selectedFile: null};
this.fileChangedHandler = this.fileChangedHandler.bind(this);
this.fileUploadHandler = this.fileUploadHandler.bind(this)
}
fileChangedHandler = (event) => {
this.setState({selectedFile:event.target.files[0]}) // asynch and skipping
console.log(this.state.selectedFile)
this.forceUpdate()
}
fileUploadHandler = () => {
const fd = new FormData()
fd.append('myFile', this.state.selectedFile, this.state.selectedFile.name)
axios.post('my-domain.com/file-upload', formData)
console.log("selected"+JSON.stringify(this.state.selectedFile))
console.log("form data : "+fd)
}
render() {
return (
<div>
<input type="file" onChange={this.fileChangedHandler} ref="file" />
<button onClick={this.fileUploadHandler}>Upload!</button>
</div>
);
}
}
i am not getting the data while consoling this.state.selectedFile it showing null.
Thanks in advance
You can hide Upload Button initially and show it after selected file is set to state in a callback.
fileChangedHandler = (event) => {
this.setState({selectedFile:event.target.files[0], showUploadButton:true}, () =>{
// file has been selected
console.log(this.state.selectedFile) <-- console.log in callback
}) // asynch and skipping
this.forceUpdate()
}
render
{this.state.showUploadButton && <button onClick={this.fileUploadHandler}>Upload!</button>}
Now upload button will appear once the file has been set to state, then you'll get it.
However your problem relies here -
this line should be inside the callback after you set the state as I mentioned above.
console.log(this.state.selectedFile)
Callback of setState method makes sure that state has been set.
update
fileChangedHandler = (event) => {
this.setState({selectedFile:event.target.files[0], showUploadButton:true}, () => {
console.log(this.state.selectedFile)
this.forceUpdate()
})
}
so why this worked?
React setState method is asynchronous which is why you receive confirmation via callback when a state is set in component.
Another question may arise in your mind that why its asynchronous whether it does not call any external resource to set state (API or server call).
The answer in simple words is -
Because on every state change in React component it re-renders render() method or Virtual DOM. Now suppose you have 100s of states to be set in a component on one click, Then this should be rendering virtual DOM 100s of times. Which react actually doesn't do.
It creates batches of states. Say bifurcation of 100s of state into 2-3 batches. Now from UI perspective it accepts multiple states at once but set them in batches by grouping the states into a few batches that keeps UI free and in non blocking state.
That is why the term asynchronous is used to setState.
Using setState callback to call forceUpdate is unneccessary - setting state will force update (new render) ;)
Callback is usable when setting state sholud fire some actions after change is done - in this case following action is trigerred manually. KISS & readable ;)
fileChangedHandler = (event) => {
this.setState({selectedFile:event.target.files[0]})
}
you can disable upload button with simple condition
render() {
return (
<div>
<input type="file" onChange={this.fileChangedHandler} ref="file" />
<button onClick={this.fileUploadHandler} disabled={!this.state.selectedFile}>Upload!</button>
</div>
);
}
or hide it
render() {
return (
<div>
<input type="file" onChange={this.fileChangedHandler} ref="file" />
{!this.state.selectedFile &&
<button onClick={this.fileUploadHandler}>Upload!</button>
}
</div>
);
}

Updating store during rendering

I have a database as json file. During rendering I pick few objects at random and I want to update my global store when I map through the database, but calling dispatch in render() causes massive errors and I dont know how to proceed further.
Here is what I have without errors:
render() {
const fakePayload = this.props.fakePayload;
const rngPayloadId = Math.floor(Math.random() * 4);
const payload = fakePayload.map(payload => {
if (payload.payloadId === rngPayloadId) {
return payload.drugsId.map(id => {
return <tr>
<td> {id}</td>
<td>{drugs[id].name}</td>
<td><input value={undefined} type="number" name="price" /></td>
<td><button >Send</button></td>
<td><button>X</button></td>
</tr>
})
}
})
return (
<tbody>{payload}</tbody>
)
And what I would want to do is something like:
return payload.drugsId.map(id => {
this.props.dispatch(setId(randomNumber, id)
If it matters action looks like this:
export const setId = (id, drugId) => ({
type: 'SET_ID',
renderedDrugs: {
id,
drugId
}
})
What do I need to learn / do to do that?
Perhaps it is because you are dispatching an action in render. Have you tried dispatching the action on componentDidMount()?
Based on the comment:
constructor(props){
super(props);
this.state = {
rngId: 0
};
}
render(){
this.setState({rngId: Math.floor(Math.random() * 4)});
...
}
componentDidMount(){
this.props.dispatch(....);
}
Lifecycle events are a core part of React. You absolutely have to read up and understand it to a certain competency in order to use Redux. You cannot use an action inside render because an action updates your store, which updates the component, which calls render, which would also call your action, which updates your component, which calls render... I'm sure you see where this is going.
You are looking for either componentWillMount or componentDidMount. Functions inside these methods will be called once and will never call itself again while the component is mounted. Refer to the Facebook React docs on lifecycle events or other resources, like tutorials.
edit to answer your comment's question. A common paradigm for controlling rendering changes will the component is updated is to use a boolean (true/false) ternary statement, to show element or hide it (a simplification, but it works for now.) You'd be looking for something like this.
class SampleComponent {
constructor() {
this.state = {
show: false
}
}
componentDidMount() {
this.setState({show: true})
}
render() {
return (
<div>
{ this.state.show ? <div>mounted</div> : null }
</div>
)
}
There would be almost zero use cases to do something like this. componentDidMount occurs so quickly upon component mounting that you'd change this.state.show to true instantly. The html elements you were hiding using state would show as if they were never controlled with state to begin with.

Dispatched and re-rendered callback?

It's easiest to explain what I'm trying to accomplish with an example:
addContact = ev => {
ev.preventDefault();
this.props.setField('contacts', contacts => update(contacts, {$push: [{name: 'NEW_CONTACT'}]}));
this.props.setFocus(`contacts.${this.props.data.contacts.length-1}.name`);
};
In this example, this.props.setField dispatches an action which causes an extra field to be added to my form.
this.props.setFocus then attempts to focus this new field.
This won't work because the form hasn't re-rendered yet when setFocus is called.
Is there any way to get a callback for when my component has been re-rendered after a dispatch call?
If you need to see it, setField looks like this:
setField(name, value) {
if(_.isFunction(value)) {
let prevValue = _.get(data, name);
if(prevValue === undefined) {
let field = form.fields.get(name);
if(field) {
prevValue = field.props.defaultValue;
}
}
value = value(prevValue);
}
dispatch(actions.change(form.id, name, value));
},
I would put
this.props.setFocus(`contacts.${this.props.data.contacts.length-1}.name`);
in componentDidUpdate and I would call it on some condition. Like let's say, prevProps.data.contact.length < this.props.data.contacts.
UPDATE
You should keep this:
addContact = ev => {
ev.preventDefault();
this.props.setField('contacts', contacts => update(contacts, {$push: [{name: 'NEW_CONTACT'}]}));
};
In a parent component, and in that component you will render all the sub components:
render() {
return {
<div>
{contacts.map(c => <ContactComponent key='blah' contact={c}>)}
<a onClick={addContact}>Add Contact</a>
</div>
};
}
Then your contact component, will be as you like, the same goes for all the other elements you want to accommodate with this functionality.
At that point, you're asking:
Where is the focus thingy?
What you need for this abstraction-ish is higher order composition. I will give you an example, but please make time to read about HOCs.
This will be you HOC:
function withAutoFocusOnCreation(WrappedComponent) {
// ...and returns another component...
return class extends React.Component {
componentDidMount() {
// contacts string below can be changed to be handled dynamically according to the wrappedComponent's type
// just keep in mind you have access to all the props of the wrapped component
this.props.setFocus(`contacts.${this.props.data.contacts.length-1}.name`);
}
render() {
return <WrappedComponent {...this.props} />;
}
};
}
And then in each child component you can use it as a decorator or just call it with your HOC and that's all. I won't write more, but do make the time to read more about HOCs, here is the official documentation's page
official documentation's page. But you can check Dan Abramov's video on egghead as well. I hope my answer helps you, please accept it if it does :) Take care!

how should I build onClick action in my react component + Redux

I've been through many tutorials and questions on Stack but I can't find a solution. I'm just learning React/redux, trying to build OnClick action. I've got the following error "Maximum call stack size exceeded error". I got this because I'm rendering a function that's changing my state infinitely. I'm trying to deal with my <button onClick={DisplayTable(click)}>cool</button> differently but nothing seems to work.
I also know that my action and I guess my reducers works properly since when I'm dispatching my action trough the console : $r.store.dispatch({type: 'SET_TABLE_DATA'});, my state is updated properly.
Any advices ?
here is my action :
export const setTableFilter = (click) => {
return {
type: 'SET_TABLE_DATA',
click : click,
};
};
here is my reducer :
const tableFilter = (state = 0, action) => {
if(action.type === 'SET_TABLE_DATA') {
return state + 1;
}
return state;
}
and here is my component :
const DisplayTable = (click) => {
return (
<div>
<button onClick={DisplayTable(click)}>cool</button>
</div> )
}
function mapStateToProps(state) {
return {
click: state.tableFilter.click
};
};
const mapDispachToProps = (dispatch) => {
return {
DisplayTable: (click) => {dispatch (setTableFilter(click));
},
};
};
const AppTable = connect(mapStateToProps, mapDispachToProps)(DisplayTable);
export default AppTable;
I also know that I should build my reducer in a way that my state should be updated without any mutation, however I'll keep this for later ! :)
thanks.
The answer given doesn't really explain why your code was not working, so I thought I'd expand on that.
Your problem is that you are exceeding the function call stack, more commonly known as infinite recursion. The reason this is happening is because you aren't passing a function to the onClick attribute of your button, but rather invoking a function and passing its return value instead. So the following scenario is happening:
React component is mounted to the DOM
render() is called
The DisplayTable function is invoked, which dispatches an update to the store
The store updates, and passes new props to the React component
render() is called again
DisplayTable is invoked again
...and so on.
What you'll want to do instead is pass the function to the button's onClick attribute. So your component should look like this:
const Component = props => {
return (
<div>
<button onClick={props.DisplayTable}>cool</button>
</div>
);
};
In that above code snippet, I removed your click prop because it doesn't look like you're using it at all (given the code you posted in the OP).
A few tips, not a complete solution since that would not help you learn:
Your action and reducer are looking fine. You are passing the click property which is not used in the reducer. Maybe you will use it in the future but for now it is useless.
A React component function takes props as an argument:
const Comp = props => {
const click = props.click;
// ...
};
mapDispatchToProps is usually not needed. Use plain objects instead:
connect(state => state.tableFilter, { setTableFilter })(DisplayTable);
You can then access the function from props:
<button onClick={() => props.setTableFilter(click)}>cool</button>
Keep in mind: onClick takes a function!
Also the state you defined in the reducer has no property called click, instead it is a number (see correct mapStateToProps function above)

Resources