How to access mutation data from component? - javascript

This is how I extend my component:
const ComponentWithMutation = graphql(GQL_MUTATION_ACTIVATE,
{
options: (props) => ({
variables: {
foo: props.foo,
bar: props.bar,
},
}),
})(ActivateEmail);
Now inside component:
class ActivateEmail extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
const { match, mutate } = this.props;
mutate({
variables: { token: match.params.atoken },
});
}
render() {
return (
<div>
// I need to access data, error, loading here...
</div>
);
}
}
I would like to access data, error, loading. How can I do it in render method?

regarding apollo-client docs, mutation returns a promise that returns mutation information like data, error, loading, etc..
so the codes should look like:
constructor() {
this.state = {
dataLoading: true,
dataLoadError: false,
}
}
async componentDidMount() {
try {
const { match, mutate } = this.props;
const { data: { yourMutationData }, error} = await mutate({
variables: { token: match.params.atoken },
});
this.setState({
dataLoading: false,
data: yourMutationData
});
}
catch (err) {
this.setState({
dataLoading: false,
dataLoadError: true,
});
}
}
or you can use a normal promise like that:
componentDidMount() {
const { match, mutate } = this.props;
mutate({
variables: { token: match.params.atoken },
})
.then( (query) => {
console.log(query); //here you should get the same result with the code above.
this.setState({
dataLoading: false,
data: query.data.yourMutationData
});
})
.catch(err => {
this.setState({
dataLoading: false,
dataLoadError: true,
});
})
}

Related

I need to change the value of a boolean in an array in react redux

Hey everyone I need to change the value of a boolean in an array in react redux. In the default state it is set to false and I need to set it to true. I am trying to change the state with an onclick event but I cant seem to do it any help I would be happy. When I click on the button reserved it still set to false and I need it set to true so I can make changes.
here it is at the start
`
export const getRockets = createAsyncThunk(
'rocketSlice/getRockets',
async () => {
const response = await fetch('https://api.spacexdata.com/v3/rockets').then(
(data) => data.json()
);
const rocketApiInfo = response.map((rocket) => {
const rockets = {
id: rocket.rocket_id,
rocketName: rocket.rocket_name,
description: rocket.description,
flickrImages: rocket.flickr_images,
reserved: false,
};
return rockets;
});
return rocketApiInfo;
}
);
my reducers
const rocketsSlice = createSlice({
name: 'rockets',
initialState: {
rockets: [],
status: '',
loading: true,
},
reducers: {
reserveRocket(state, action) {
const newState = state.rockets.map((rocket) => {
if (rocket.id === action.payload) {
return { ...rocket, reserved: true };
}
return rocket;
});
return { ...state, rockets: newState };
},
cancelReservation(state, action) {
const newState = state.rockets.map((rocket) => {
if (rocket.id === action.payload) {
return { ...rocket, reserved: false };
}
return rocket;
});
return { ...state, rockets: newState };
},
},
`
the function
const reservedhandler = (e) => {
if (reserved === false) {
dispatch(reserveRocket(e.target.id));
} else {
dispatch(cancelReservation(e.target.id));
}
};
reserved is undefined so maybe that is the problem. But i am not sure how to fix it. Thanks guys

how to change state value in helper function file in react js

I have functions.js file and it export one function that I want to use in many files.
functions.js
import { API_URL } from "./index";
export const getData = (skip = 0, params = "") => {
this.setState({
loading: true
});
fetch(`${API_URL}items?limit=5&skip=${skip}${params}`, {
method: "GET",
credentials: "include"
})
.then(res => res.json())
.then(res => {
if (res.result.length > 0) {
let array = [];
res.result.map(item => {
let obj = item.data;
obj = Object.assign({ id: item._id }, obj);
array.push(obj);
});
this.setState({
records: array,
loading: false
});
} else {
this.setState({
next: true,
loading: false,
records: []
});
}
})
.catch(err => {
this.setState({
loading: false
});
});
};
hear this is function.js file that gets data from API and set in the state,
now, I want to use this function in items.js
items.js
import { getData } from "./../../config/functions";
import React from "react";
class Customers extends React.Component {
constructor(props) {
super(props);
this.getData = getData.bind(this);
}
componentDidMount() {
this.getData();
}
...
}
Error
TypeError: Cannot read property 'setState' of undefined
I fount this answer How to use state of one component in another file in reactjs? but it did not work for me so help me to change app.js file state from my functions.js file.
You're trying to re-bind this on an arrow function, which you cannot do. Check out this other SO question/answer for more details, but that's your problem. I'm going to edit this post with a suggestion of a more idiomatic way to write this in React.
Edit: OK I wanted to get you an answer quickly so you could unblock yourself and learn a bit more about arrow functions and this binding.
But more than just fixing this, you could improve this code significantly if you separate your api requests from your component. Right now you're mixing them up by trying to set state in your function that fetches data.
import { API_URL } from "./index";
export const getData = (skip = 0, params = "") => {
this.setState({
loading: true
});
fetch(`${API_URL}items?limit=5&skip=${skip}${params}`, {
method: "GET",
credentials: "include"
})
.then(res => res.json())
.then(res => {
// no need to declare an array and then push to it,
// that's what map is for. It will return a new array.
return res.result.map(item => {
// can also be written as return { ...item, id: item._id }
return Object.assign({ id: item._id }, obj)
});
});
// no need to catch here, you can do error handling in your component
};
import { getData } from "./../../config/functions";
import React from "react";
class Customers extends React.Component {
constructor(props) {
super(props);
this.fetchData = this.fetchData.bind(this);
}
componentDidMount() {
this.fetchData();
}
fetchData() {
getData()
.then((results) => {
this.setState({
next: results.length === 0,
records: results,
loading: false
});
})
.catch((err) => {
this.setState({ loading: false })
});
}
...
}

How to change the url in the fetch statement React JS using props?

So, I'm trying to make this ApiCalls class, which was working if I just plugged in a url into the fetch statement, but I'm trying to make it so that I can change the url depending on which button I press on the site. I want to call ApiCalls in the SearchButtons.js class where in each click function I'll specify the url I want to use. It's not working and it's definitely something to do with the props, idk how else to pass in the a
constructor(props) {
super(props);
this.state = {
error: null,
isLoaded: false,
items: [],
//url: {props.url}////////////////////////////// this doesnt work.
//if i dont use brackets it compiles, but I get this error
//Error: Unexpected token < in JSON at position 0
};
}
componentDidMount() {
fetch(this.url)//////////////////////////////////////////////
.then(res => res.json())
.then(
(result) => {
this.setState({
isLoaded: true,
items: result.articles
});
},
(error) => {
this.setState({
isLoaded: true,
error
});
}
)
}
render() {
const { error, isLoaded, items } = this.state;
if (error) {
return <div>Error: {error.message}</div>;
} else if (!isLoaded) {
return <div>Loading...</div>;
} else {
console.log(items);
return (
<ul>
{items.map(item => (
<ArticleCard key={item.title} title={item.title} desc={item.description} imgsrc={item.urlToImage} url={item.url}/>
))}
</ul>
);
}
}
}
export default ApiCalls;
import React, { Component } from 'react';
import ApiCalls from "./ApiCalls";
class SearchButton extends React.Component {
handleClick = () => {
console.log('this is:', this);
return (<ApiCalls url="myUrlHasMyAPIKeySoThisIsAPlaceHolder/>); ////////////////////////
///this is where I want to specify the url
}
You can achieve this by simply doing this.
import React, { Component } from 'react';
import Child from "./ApiCalls";
class SearchButton extends React.Component {
handleClick = () => {
console.log('this is:', this);
return (<ApiCalls url="myUrl"/>);
}
....
And In Child Component.
import React, { Component } from 'react';
class Child extends React.Component {
constructor(props) {
super(props);
this.state = {
error: null,
isLoaded: false,
items: [],
};
}
componentDidMount() {
fetch(this.props.url){/* Access url from props */}
.then(res => res.json())
.then(
(result) => {
this.setState({
isLoaded: true,
items: result.articles
});
},
(error) => {
this.setState({
isLoaded: true,
error
});
}
)
}
....
Hope, this will help you.

How to use redux saga with infinite scroll on Flatlist?

I'm developing my first React Native app and it is the first time I'm using redux and redux saga. So I've built a Flatlist to have infinite scroll with a API endpoint tha returns posts (10 per page). But I don't know how to use the reducers to return the posts, control the loading indicator and keep track of the page number in the store, using redux saga.
My code is the following:
Home.js
this.state = {
page: 1,
totalPages: 10,
loading: false,
}
componentDidMount() {
this.loadMorePosts();
}
loadMorePosts = () => {
this.setState(() => { loading: true });
this.setState(() => { page: this.state.page++ });
this.props.loadPosts(this.state.page);
}
<AnimatedFlatList
...
onEndReached={this.loadMorePosts}
onEndReachedThreshold={0.2}
/>
const mapStateToProps = state => ({
posts: state.posts,
});
Posts Action
export function loadPosts(page){
return {
type: Types.FETCH_POSTS,
payload: { page }
};
}
Posts saga
export function* fetchPosts(action) {
const response = yield call(api.get, `/posts/${action.payload.page}`);
yield put({ type: Types.LOAD_POSTS, payload: { posts: response.data } });
}
Posts Reducer
export default function posts(state = initialState, action) {
switch(action.type) {
case Types.LOAD_POSTS:
return [ ...state, ...action.payload.posts ];
default:
return state;
}
}
With this I can fetch the posts and load into the Flatlist, but if I change screens I lose track of the actual page number, that will be set to 0 again in the Home.js constructor. And there is no visual feedback since the loading state is not defined with the mapStateToProps function...
Can anyone help me solve this problem?
Expanding on a comment: (Not tested code but principle is there).
Saga
export function* fetchPosts(action) {
try {
yield put({ type: Types.LOAD_POSTS_START, payload: { page: action.payload.page } });
const response = yield call(api.get, `/posts/${action.payload.page}`);
yield put({ type: Types.LOAD_POSTS, payload: { posts: response.data } });
}
catch {
//perhaps roll back page count?
yield put({ type: Types.LOAD_POSTS_END, payload: { } });
}
}
Reducer
const initialState = {
isLoading: false,
currentPage: 0,
posts: []
}
export default function posts(state = initialState, action) {
switch(action.type) {
case Types.LOAD_POSTS_START:
return {
...state,
currentPage: action.payload.page,
isLoading: true
};
case Types.LOAD_POSTS_END:
return {
...state,
isLoading: false
};
case Types.LOAD_POSTS:
return {
...state,
isLoading: false,
posts: [ ...state.posts, ...action.payload.posts ]
};
default:
return state;
}
}
Then in your component connect to this state rather than have it stored in the components state object
Make a saga/task that just does a fetch and returns a promise like this:
const fetchAction = (input, init) => {
let resolve;
const promise = new Promise(resolveArg => resolve = resolveArg)
return { type:'FETCH_ACTION', input, init, promise, resolve }
}
function fetchActionWorker({ input, init, resolve}) {
const res = yield call(fetch, input, init);
resolve(res);
}
function* fetchActionWatcher() {
yield takeEvery('FETCH_ACTION', fetchWorker);
}
Then use it like this:
class List extends Component {
render() {
return <Button title="fetch" onPress={this.doFetch} />
}
doFetch = async () => {
const res = await dispatch(fetchAction('url', { method:'GET' })).promise;
}
}
Calling the fetch action gives you a promise right away.

Wait for AJAX request ( React, Redux)

Need display data after AJAX call will be done.
My Reducer:
import { INCOME_PROFILE } from '../actionTypes'
import Immutable from 'immutable'
const initialUserState = [];
const profileReducer = function(state = initialUserState, action) {
//console.log('actiondata in reducer:' + action.data + action.type);
switch(action.type) {
case 'INCOME_PROFILE_IS_LOADING':
return Object.assign({}, state, { hh: action.hasFetched });
case 'INCOME_PROFILE':
return Object.assign({}, state, { course_list: action.data, hh: action.hasFetched });
default:
return state;
}
}
export default profileReducer
My action creator:
export function GET_ITEM_REQUEST() {
return {
type: INCOME_PROFILE_IS_LOADING,
hasFetched: false,
}
}
function receiveData(json) {
return{
type: INCOME_PROFILE,
data: json,
hasFetched: true
}
};
export function IncomeProfileList () {
return dispatch => {
return (
axios.post(Api.getURI("profile"),{}, {
headers: { 'X-Authenticated-Userid': '15000500000#1' }
}).then(function (response) {
//console.log(response.data);
dispatch(receiveData(response.data.body));
})
.catch((error) => {
console.log(error);
})
)
}
}
My component:
class IncomeProfile extends Component {
constructor(props) {
super(props)
}
componentDidMount() {
this.props.IncomeListProfile();
}
render() {
console.log(this.props.isloading);
if (this.props.isloading) {
return <p>Sorry! There was an error loading the items</p>;
}
}
}
const mapDispatchToProps = function(dispatch) {
return {
IncomeListProfile: () => dispatch(IncomeProfileList())
}
}
const mapStateToProps = function(state) {
//var mystore = state.toJS()
var mystore = state.getIn(['incomeProfileList'])['course_list'];
var mystored = state.getIn(['incomeProfileList']);
console.log(mystored.hh);
var copy = Object.assign({}, mystore);
return {
items: copy.course_list,
isloading: mystored.hh
};
}
I need next: While response not finish, I no need to display data. Condition if not works now
console.log at first time get undefined - think must be false, but it not state false. and second time it's getting true.
You don't need property 'isLoading' - just handle 2 cases in which you have your data and you haven't. Put this condition in render() function, because the component is going to refresh after passing data through the reducer. Syntax will be something like this in your case:
render() {
if(!this.props.items) {
return <div>Loading...</div>;
} else {
return (
<div>Display your data!</div>
);
}
}

Categories

Resources