How can I update a React component depending on redux action outcome - javascript

I have a React component that contains an input. The component dispatches a redux action that calls an API. I want to display a "SUCCESS" message per component but I can't work out how to get the message, other than through the reducer? But if I was to do it through the reducer it would just be a general message that would update all the form components?
//COMPONENT
export default class Stock extends React.Component {
constructor(props) {
super(props);
this.state = {
value: "",
id: this.props.hit['Name of Item'],
}
}
handleChange(e){
e.preventDefault();
this.setState({value: e.target.value});
}
handleClick(e){
e.preventDefault();
this.props.dispatch(updatePosStock(this.state.value, this.state.id));
}
render() {
return (
<div className="item">
<p>{this.props.hit['Name of Item']}: {this.props.hit.Quantity}</p>
<p>Stock ID: {this.props.hit.objectID}</p>
<div className="control">
<input className="input" value={this.state.value} type="text" onChange={this.handleChange.bind(this)} />
<a href="" onClick={this.handleClick.bind(this)} >save</a>
//MESSAGE TO DISPLAY HERE DEPENDING ON handleClick
</div>
</div>
)
}
}
//ACTION
export function updatePosStock(product, stock){
return function(dispatch) {
axios.post('/api/v1/product/', {
product,
stock
})
.then((response) => {
//console.log(response.data);
return response.data;
//dispatch({type: 'FETCH_PRODUCT_FULFILLED', payload: response.data})
})
.catch((error) => {
console.log(error);
})
}
}

If I understand, this component is rendered multiple times on the page and when you trigger action on one, message gets distributed to all the components (because it is not distinguishable between components.
So, I'd set message to be something like:
[{
message: 'Success',
item: itemId
},
...]
and when sending a data to api I'd send for which item it is being sent and would just push this message to message array, then in place where you want to display message you put
{this.displayMessage(messagesVar, index) || ''}
So when display message is not falsy it displays message.
messageVar is what your reducer updates.
Display message function is smt like
(messages, id) => {
currentComponentMsgs = messages.filter((item) => item.id === id);
return currentComponentMsgs.length > 0
? currentComponentMsgs.mat((item) => item.message).reduce((a,b) => a + ', ' + b}
: '');
}
I did not have time to check if it all works, but if you propagate id of each elemnt properly, using this pattern you can even have multiple messages recorder in messagesVar, so it serves as kind of a mailbox for components. You can use it on all of your components.

There are two ways you can achieve this
1. save your response message (e.g. 'success') in the form of an array
In this case you can define a state such as feedback: [], for example. Then you will set the message this way
...
//dispatch({type: 'FETCH_PRODUCT_FULFILLED', payload: [ product : response.data]})
..
Then in your component you do the following
...
<input className="input" value={this.state.value} type="text" onChange={this.handleChange.bind(this)} />
<a href="" onClick={this.handleClick.bind(this)} >save</a>
//MESSAGE TO DISPLAY HERE DEPENDING ON handleClick
<p className="feedback">{this.props.feedback[this.state.value] || ''}</p>
Depending on how you import your states in to your components.
2. Create another property in your redux state or inside this.state={}
for example inputKey. Then set inputKey value through handleClick(e, inputKeyValue)
You could pass inputKey as a third parameter to the function dispatched and continue with display as should above in you component.
I strongly recommend that you handle all your states with redux and avoid using this.state and this.setState() as much as you can.
I hope it helps.

Use callback for your action:
handleClick(e){
e.preventDefault();
this.props.dispatch(updatePosStock(this.state.value, this.state.id,
(code, message)=>{if (code==='OK')this.setState({displayMessage:true, message:"Successfully saved"})}));
}
///ACTION
export function updatePosStock(product, stock, callback){
return function(dispatch) {
axios.post('/api/v1/product/', {
product,
stock
})
.then((response) => {
callback('OK')
return response.data;
//dispatch({type: 'FETCH_PRODUCT_FULFILLED', payload: response.data})
}) .catch((error) => { callback(error.code, error.message)
console.log(error);
})
}
}

Related

infinite api calls in componentDidUpdate(prevProps) despite conditional that compares current prop with previous prop

My problem is in my subcomponent files where every state update in the parent component keeps re-rendering the subcomponents infinitely by making infinite api calls with the default or updated props value passed to the child components. I have a User directory page which contains multiple components in a single page.
class Users extends Component {
constructor(props) {
super(props);
this.state = {
user: "",
listOfUsers: [],
socialData:[],
contactData:[],
videoData:[],
detailsData:[]
}
}
componentDidMount(){
//api call that gets list of users here
//set response data to this.state.listOfUsers
}
userHandler = async (event) => {
this.setState({
user: event.target.value,
});
};
render() {
return (
<div>
<div>
<select
value={this.state.user}
onChange={this.userHandler}
>
// list of users returned from api
</select>
</div>
<div>
<Social Media user={this.state.user} />
<Contact user={this.state.user}/>
<Video user={this.state.user}/>
<Details user={this.state.user}/>
</div>
</div>
);
}
}
I have 1 API call for the parent component Users, and 4 for each of the subcomponents: Social Media, Contact, Video, and Details. The Users api will return a list of users in a dropdown and the value of the user selected is then fed to the other four API's. i.e. https://localhost:3000/social_media?user=${this.state.user}. Thus, the four subcomponents' API is dependent on the Users API. I currently have the parent api call in a componentDidMount() and the other 4 api calls in their respective subcomponents and use props to pass down the value of the user selected in the parent to the subcomponents. Each of the api calls is in a componentDidUpdate(prevProps). All the subcomponents follow this structure:
class Social Media extends Component {
constructor(props) {
super(props);
this.state = {
user: "",
socialData:[],
}
}
componentWillReceiveProps(props) {
this.setState({ user: this.props.user })
}
componentDidUpdate(prevProps){
if (this.props.user !== prevProps.user) {
// make api call here
fetch (`https://localhost:3000/social_media?user=${this.state.user}`)
.then((response) => response.json())
.catch((error) => console.error("Error: ", error))
.then((data) => {
this.setState({ socialData: Array.from(data) });
}
}
render() {
return (
{this.socialData.length > 0 ? (
<div>
<Social Media data={this.state.socialData}/>
</div>
)
:
(<div> Loading ... </div>)
);
}
}
Abortive attempt to answer your question
It's hard to say exactly what's going on here; based on the state shown in the Users component, user should be a string, which should be straightforward to compare, but clearly something is going wrong in the if (this.props.user !== prevProps.user) { comparison.
If we could see the results of the console.log(typeof this.props.user, this.props.user, typeof prevProps.user, typeof prevProps.user) call I suggested in my comment, we'd probably have a better idea what's going on here.
Suggestions that go beyond the scope of your question
Given the moderate complexity of your state, you may want to use some sort of shared state like React's Context API, Redux, or MobX.. I'm partial toward the Context API, as it's built into React and requires relatively less setup.
(Then again, I also prefer functional components and hooks to classes and componentDidUpdate, so my suggestion may not apply to your codebase without a rewrite.)
If this.props.user is an object, then this.props.user !== prevProps.user will evaluate to true because they are not the same object. If you want to compare if they have the same properties and values (i.e. they are shallowly equal) you can use an npm package like shallow-equal and do something like:
import { shallowEqualObjects } from "shallow-equal";
//...
componentDidUpdate(prevProps){
if (!shallowEqualObjects(prevProps.user, this.props.user)) {
// make api call here
fetch (`https://localhost:3000/social_media?user=${this.state.user}`)
.then((response) => response.json())
.catch((error) => console.error("Error: ", error))
.then((data) => {
this.setState({ socialData: Array.from(data) });
}
}

How to get Form submit value in react-redux with rails backend?

I have a Rails backend, with a list of ingredients. I am trying to use a Search Bar (form) to input an ingredient ID to find the appropriate ingredient.
When I click the submit button of the form, I want the dispatch function getIngredient, with ID as argument, but with the approaches I have tried, I am not getting what is supposed to be the ID; it is undefined, or simply "an event". I only saw the error message briefly, so I don't have more details then that. It simply refreshes state, erasing the error messages.
I have tried having the value of the form onSubmit={() => getIngredient(id)}, it then returns as an event object. with onSubmit={() => this.props.getIngredient(id)} it returns undefined. Stating it simply as onSubmit={getIngredient(id)} also didn't work.
I have console.logged everything, and I found that it errors when it hits the dispatch action in getIngredient(id), because the id is undefined.
The getIngredients method I have works, it was when trying to only grab one ingredient I have had trouble. I have read everything I could find about redux, react-redux, and even reselect to try and target the issue, and I have hit this wall.
IngredientForm.js component:
<form onSubmit={(id) => getIngredient(id)}>
<label value="Find Ingredient">
<input type="text" placeholder="Search By Id" />
</label>
<input type="submit" value="Find Ingredient" />
</form>
const mapDispatchToProps = { getIngredient }
const mapStateToProps = (state, props) => {
const id = props.id;
return {
ingredients: state.ingredients.filter(ingredient => ingredient.id === id)
};
};
Dispatch action creator in actions.js:
export function getIngredient(id) {
return dispatch => {
console.log("trying to get id to show up =>", id)
dispatch(getIngredientRequest(id));
return fetch(`v1/ingredients`)
.catch(error => console.log(error))
.then(response => response.json())
.then(json => dispatch(getIngredientSuccess(json)))
.catch(error => console.log(error));
}
}
action:
export function getIngredientRequest(id) {
return { type: GET_INGREDIENT_REQUEST, id };
}
reducer.js
function rootReducer(state = initialState, action) {
console.log(action.type);
switch (action.type) {
case "GET_INGREDIENTS_SUCCESS":
return { ...state, ingredients: action.json.ingredients }
case "GET_INGREDIENTS_REQUEST":
console.log('Ingredients request received')
return
case "HIDE_INGREDIENTS":
console.log('Ingredients are being hidden')
return { ...state, ingredients: [] }
case "GET_INGREDIENT_REQUEST":
console.log('One Ingredient request received:', "id:", action.id)
return
case "GET_INGREDIENT_SUCCESS":
console.log('GET_INGREDIENT_SUCCESS')
return {
...state,
ingredients: action.json.ingredients.filter(i => i.id)
}
default:
return state
}
}
Server terminal window output:
Processing by StaticController#index as HTML
Parameters: {"page"=>"ingredients"}
Rendering static/index.html.erb within layouts/application
Rendered static/index.html.erb within layouts/application (0.9ms)
Completed 200 OK in 9ms (Views: 7.9ms | ActiveRecord: 0.0ms)```
That's because in React Synthetic Events - like onSubmit - the first parameter in the callback is always the Event object.
I recommend you to use controlled inputs. That means you keep the id value in the component's state and assign that value to the input. Whenever the user types a new id, you update the state and assign the new value to the input.
class Form extends React.Component {
state = {
id: ""
};
handleInputChange = event => {
this.setState({ id: event.target.value });
};
render() {
return (
<form
onSubmit={(event) => {
event.preventDefault();
getIngredient(this.state.id);
}}
>
<label value="Find Ingredient">
<input
type="text"
placeholder="Search By Id"
onChange={this.handleInputChange}
value={this.state.id}
/>
</label>
<input type="submit" value="Find Ingredient" />
</form>
);
}
}

getDerivedStateFromProps, change of state under the influence of changing props

I click Item -> I get data from url:https: // app / api / v1 / asset / $ {id}. The data is saved in loadItemId. I am moving loadItemId from the component Items to the component Details, then to the component AnotherItem.
Each time I click Item the props loadItemId changes in the getDerivedStateFromProps method. Problem: I'll click Element D -> I see in console.log 'true', then I'll click Element E --> It display in console.log true andfalse simultaneously, and it should display only false.
Trying to create a ternary operator {this.state.itemX ['completed'] ? this.start () : ''}. If {this.state.itemX ['completed'] call the function this.start ()
Code here: stackblitz
Picture: https://imgur.com/a/OBxMKCd
Items
class Items extends Component {
constructor (props) {
super(props);
this.state = {
itemId: null,
loadItemId: ''
}
}
selectItem = (id) => {
this.setState({
itemId: id
})
this.load(id);
}
load = (id) => {
axios.get
axios({
url: `https://app/api/v1/asset/${id}`,
method: "GET",
headers: {
'Authorization': `Bearer ${token}`
}
})
.then(response => {
this.setState({
loadItemId: response.data
});
})
.catch(error => {
console.log(error);
})
}
render () {
return (
<div >
<Item
key={item.id}
item={item}
selectItem={this.selectItem}
>
<Details
loadItemId={this.state.loadTime}
/>
</div>
)
}
Item
class Item extends Component {
render () {
return (
<div onClick={() => this.props.selectItem(item.id}>
</div>
)
}
}
Details
class Details extends Component {
constructor() {
super();
}
render () {
return (
<div>
<AnotherItem
loadItemId = {this.props.loadItemId}
/>
</div>
)
}
}
AnotherItem
class AnotherItem extends Component {
constructor() {
super();
this.state = {
itemX: ''
};
}
static getDerivedStateFromProps(nextProps, prevState) {
if(nextProps.loadItemId !== prevState.loadItemId) {
return { itemX: nextProps.loadItemId }
}
render () {
console.log(this.state.itemX ? this.state.itemX['completed'] : '');
{/*if this.state.loadX['completed'] === true, call function this.start()*/ }
return (
<button /*{this.state.loadX['completed'] ? this.start() : ''}*/ onClick={this.start}>
Start
</button>
);
}
}
here:
selectItem = (id) => {
this.setState({
itemId: id
})
this.load(id);
}
you call setState(), then 'Item' and 'Details' and 'AnotherItem' call their render method. so you see log for previous 'loadItemId'.
when 'load' method work done. here:
this.setState({
loadItemId: response.data
});
you setState() again, then 'Item' and 'Details' and 'AnotherItem' call their render method again. in this time you see log for new 'loadItemId'.
solution
setState both state in one place. after load method done, instead of:
this.setState({
loadItemId: response.data
});
write:
this.setState({
itemId: id,
loadItemId: response.data
});
and remove:
this.setState({
itemId: id
})
from 'selectItem' method.
Need some clarification, but think I can still address this at high level. As suggested in comment above, with the information presented, it does not seem that your component AnotherItem actually needs to maintain state to determine the correct time at which to invoke start() method (although it may need to be stateful for other reasons, as noted below).
It appears the functionality you are trying to achieve (invoke start method at particular time) can be completed solely with a comparison of old/new props by the componentDidUpdate lifecycle method. As provided by the React docs, getDerivedStateFromProps is actually reserved for a few 'rare' cases, none of which I believe are present here. Rather, it seems that you want to call a certain method, perhaps perform some calculation, when new props are received and meet a certain condition (e.g., not equal to old props). That can be achieved by hooking into componentDidUpdate.
class AnotherItem extends Component {
constructor(props) {
super(props);
this.state = {}
}
start = () => { do something, perform a calculation }
// Invoked when new props are passed
componentDidUpdate(prevProps) {
// Test condition to determine whether to call start() method based on new props,
// (can add other conditionals limit number of calls to start, e.g.,
// compare other properties of loadItemId from prevProps and this.props) .
if (this.props.loadItemId && this.props.loadItemId.completed === true) {
//Possibly store result from start() in state if needed
const result = this.start();
}
}
}
render () {
// Render UI, maybe based on updated state/result of start method if
// needed
);
}
}
You are encountering this behaviour because you are changing state of Items component on each click with
this.setState({
itemId: id
})
When changing its state, Items component rerenders causing AnotherItem to rerender (because that is child component) with it's previous state which has completed as true (since you've clicked element D before). Then async request completes and another rerender is caused with
this.setState({
loadItemId: response.data
});
which initiates another AnotherItem rerender and expected result which is false.
Try removing state change in selectItem and you'll get desired result.
I'd suggest you read this article and try to structure your code differently.
EDIT
You can easily fix this with adding loader to your component:
selectItem = (id) => {
this.setState({
itemId: id,
loading: true
})
this.load(id);
}
load = (id) => {
axios.get
axios({
url: `https://jsonplaceholder.typicode.com/todos/${id}`,
method: "GET"
})
.then(response => {
this.setState({
loading: false,
loadItemId: response.data
});
})
.catch(error => {
console.log(error);
})
}
render() {
return (
<div >
<ul>
{this.state.items.map((item, index) =>
<Item
key={item.id}
item={item}
selectItem={this.selectItem}
/>
)
}
</ul>
{this.state.loading ? <span>Loading...</span> : <Details
itemId={this.state.itemId}
loadItemId={this.state.loadItemId}
/>}
</div>
)
}
This way, you'll rerender your Details component only when you have data fetched and no unnecessary rerenders will occur.

Fetch one JSON object and use it reactjs

I'm very new to JS and ReactJS and I try to fetch an endpoint which gives me a JSON Object like this :
{"IDPRODUCT":4317892,"DESCRIPTION":"Some product of the store"}
I get this JSON Object by this endpoint :
http://localhost:3000/product/4317892
But I dont how to use it in my react application, I want to use those datas to display them on the page
My current code looks like this but it's not working and I'm sure not good too :
import React, {Component} from 'react';
class Products extends Component {
constructor(props){
super(props);
this.state = {
posts: {}
};
};
componentWillMount() {
fetch('http://localhost:3000/product/4317892')
.then(res => res.json())
.then(res => {
this.setState({
res
})
})
.catch((error => {
console.error(error);
}));
}
render() {
console.log(this.state)
const { postItems } = this.state;
return (
<div>
{postItems}
</div>
);
}
}
export default Products;
In the console.log(this.state) there is the data, but I'm so confused right now, dont know what to do
Since I'm here, I have one more question, I want to have an input in my App.js where the user will be able to type the product's id and get one, how can I manage to do that ? Passing the data from App.js to Products.js which is going to get the data and display them
Thank you all in advance
Your state doesn't have a postItems property which is considered undefined and react therefore would not render. In your situation there is no need to define a new const and use the state directly.
Also, when you setState(), you need to tell it which state property it should set the value to.
componentWillMount() {
fetch('http://localhost:3000/product/4317892')
.then(res => res.json())
.then(res => {
this.setState({
...this.state, // Not required but just a heads up on using mutation
posts: res
})
})
.catch((error => {
console.error(error);
}));
}
render() {
console.log(this.state)
return (
<div>
<p><strong>Id: {this.state.posts.IDPRODUCT}</strong></p>
<p>Description: {this.state.posts.DESCRIPTION}</p>
</div>
);
}
I have got 3 names for the same thing in your js: posts, postItems and res.
React can not determine for you that posts = postItems = res.
So make changes like this:
-
this.state = {
postItems: {}
};
-
this.setState({
postItems: res
});
-
return (
<div>
{JSON.stringify(postItems)}
<div>
<span>{postItems.IDPRODUCT}</span>
<span>{postItems.DESCRIPTION}</span>
</div>
</div>
);
{postItems["IDPRODUCT"]}
Will display the first value. You can do the same for the other value. Alternatively, you can put
{JSON.stringify(postItems)}
With respect to taking input in the App to use in this component, you can pass that input down through the props and access it in this component via this.props.myInput. In your app it'll look like this:
<Products myInput={someInput} />

How store data from fetch

I'm pretty new in React and need some help.
I wanted display data from a movie database based on the search term. I'm using fetch inside my getMovies methode to get the data. The data is stored in data.Search but I don't know how to access it and store it in a variable.
class DataService
{
getMovies (searchTerm) {
//let dataStorage; //store somehow data.Search inside
fetch("http://www.omdbapi.com/?s=" + searchTerm, {
method: 'get'
})
.then((resp) => resp.json())
.then(function(data) {
return data.Search;
}).catch(function(error) {
console.log(error);// Error :(
});
}
//return dataStorage; //return data.Search
}
The below code is the correct react's way for your case, as simple as this:
import React from 'react';
export default class DataService extends React.Component {
constructor(props) {
super(props);
this.state = {
search_data: [], //for storing videos
};
this.getMovies = this.getMovies.bind(this); //bind this function to the current React Component
}
getMovies (searchTerm) {
fetch("http://www.omdbapi.com/?s=" + searchTerm, {
method: 'get'
})
.then((resp) => resp.json())
.then((data) => { //so that this callback function is bound to this React Component by itself
// Set state to bind search results into "search_data"
// or you can set "dataStorage = data.Search" here
// however, fetch is asynchronous, so using state is the correct way, it will update your view automatically if you render like I do below (in the render method)
this.setState({
search_data: data.Search,
});
});
}
componentDidMount() {
this.getMovies(); //start the fetch function here after elements appeared on page already
}
render() {
return (
{this.state.search_data.map((video, i) =>{
console.log(video);
// please use the correct key for your video below, I just assume it has a title
return(
<div>{video.title}</div>
)
})}
);
}
}
Feel free to post here any errors you may have, thanks
There are several ways of doing asynchronous tasks with React. The most basic one is to use setState and launch a Promise in the event handler. This might be viable for basic tasks but later on, you will encounter race conditions and other nasty stuff.
In order to do so, your service should return a Promise of results. On the React side when the query changes, the service is called to fetch new results. While doing so, there are few state transitions: setting loading flag in order to notify the user that the task is pending and when the promise resolves or rejects the data or an error is stored in the component. All you need is setState method.
More advanced techniques are based on Redux with redux-thunk or redux-saga middlewares. You may also consider RxJS - it is created especially for that kind of stuff providing debouncing, cancellation and other features out of the box.
Please see the following example of simple search view using yours DataService.
class DataService
{
//returns a Promise of search results, let the consumer handle errors
getMovies (searchTerm) {
return fetch("http://www.omdbapi.com/?s=" + searchTerm, {
method: 'get'
})
.then((resp) => resp.json())
.then(function(data) {
return data.Search;
})
}
}
const SearchResults = ({data, loading, error}) =>
<div>
{
data && data.map(({Title, Year, imdbID, Type, Poster}) =>
<div key={imdbID}>
{Title} - {Year} - {Type}
<img src={Poster} />
</div>
)
}
{loading && <div>Loading...</div>}
{error && <div>Error {error.message}</div>}
</div>
class SearchExample extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this)
this.state = {
data: [],
loading: false
};
}
handleChange(event) {
const service = new DataService();
//search query is target's value
const promise = service.getMovies(event.target.value);
//show that search is being performed
this.setState({
loading: true
})
//after the promise is resolved display the data or the error
promise.then(results => {
this.setState({
loading: false,
data: results
})
})
.catch(error => {
this.setState({
loading: false,
error: error
})
})
}
render() {
return (
<div>
<input placeholder="Search..." onChange={this.handleChange} type="search" />
<SearchResults data={this.state.data} loading={this.state.loading} error={this.state.error} />
</div>
)
}
}
ReactDOM.render(<SearchExample />, document.getElementById("app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>

Categories

Resources