React-native / Redux - error with state - react-redux

So far I have been following various tutorials. This time I'm trying to build things from scratch (kind of). For now the following is supposed to display part of state. Later on I'll play with making it do calculations,etc. Still I get an error:
Cannot read property 'count' of undefined
So I use mapStateToProps and the first step I'd like to do is to get it to display this.props.count and this.props.step. Once I've done it I'll modify it to do more complex things.
Here's the component and below there's a link to the whole code that I put on github.
import React, { Component } from 'react';
import { View, Text } from 'react-native';
import { connect } from 'react-redux';
import { getCounter } from '../actions';
class CounterBoard extends Component {
render() {
return (
<View>
<Text>BELOW SHOULD DISPLAY 0</Text>
<Text>{this.prop.count}</Text>
</View>
);
}
}
const mapStateToProps = (state) => {
return {
count: state.count,
step: state.step
};
};
export default connect(mapStateToProps, { getCounter })(CounterBoard);
https://github.com/wastelandtime/calculator
Edit: Thank you for the 'prop' => 'props' pointer. Now I have the following error:
ExceptionsManager.js:63 Objects are not valid as a React child (found: object with keys {count, step}). If you meant to render a collection of children, use an array instead or wrap the object using createFragment(object) from the React add-ons. Check the render method of `Text`.

After going through your code on Github, I couldn't help but notice that in your action, you return the following object:
export const getCounter = (count) => {
return {
type: GET_COUNTER,
payload: count
};
};
If you intend to return the count, I assume that action.payload should contain a number here and not an object.
However, in your reducer, you return:
case GET_COUNTER:
return action.payload.count;
Assuming from the code in your CounterBoard Component and CalcReducer, you probably wanted to merge your payload into the initial state before returning from the reducer?
You might also need to pass and argument while dispatching the getCounter action, for the component to work as expected.

Related

How to build a Model Layer in Vue3 just like other MVC language?

my name is DP, I have 2 years Vue2 experience, but I am new to Vue3. I am learning Vue3 recently, as I found the "setup(Composition API)" just like the "Controller(in MVC)" that I did in other language, so I am trying to build my test Vue3 project in MVC way, but I go some problem can anyone help? thx!
MVC Plan
M - use class
V - use <template> ... </template>
C - use setup
My Problem
working: using loadTopic_inSetup().then() in setup is working, because topicList_inSetup is defined in setup() too.
not working: using loadTopic_inModel() in setup is not working, I guess some kind data keep problem, because in console I can see the data already got from API
as u can see, I am not expert for js/ts, I am a backend developer, so if you know how to do it, plz help thx very much.
BTW, VUE is greet, I love it.
My Code
//APIBased.ts
import { ajax } from "#/lib/eeAxios"
export class APIBased {
//load data with given url and params
loadData(apiPath: string, params?: object): Promise<any> {
apiPath = '/v1/'+apiPath
return ajax.get(apiPath, params)
}
}
//Topic.ts
import { APIBased } from "./APIBased";
import { ref } from 'vue'
export class Topic extends APIBased {
//try keep data in model
topicList: any = ref([]);
constructor() {
super()
}
//direct return ajax.get, let setup do the then+catch
loadTopic_inSetup() {
return super.loadData('topics', { t_type_id: 1 })
}
//run ajax get set return data to this.topicList, keep data in model
loadTopic_inModel() {
super.loadData('topics', { t_type_id: 1 }).then((re) => {
console.log(re.data)
this.topicList = re.data
})
}
}
//EETest.vue
<template>
<EELayoutMainLayout>
<template v-slot:mainContent>
<h1>{{ "Hello Vue3 !!" }}</h1>
<hr/>
{{to.topicList}} //not working... just empty array
<hr/>
{{topicList_inSetup}} //working... topic list return from API show here.
</template>
</EELayoutMainLayout>
</template>
<script lang="ts">
import { defineComponent, getCurrentInstance, ref } from 'vue'
import EELayoutMainLayout from '#/components/eeLayout/EELayoutMainLayout.vue'
import { Topic } from "#/models/Topic";
export default defineComponent({
name: 'EETest',
props: {
},
setup() {
let topicList_inSetup = ref([])
const to = new Topic()
//try keep data in setup, it's working
to.loadTopic_inSetup().then((re) => {
topicList_inSetup.value = re.data
console.log(re.data)
})
//try keep data in model, the function is run, api return get, but data not show, even add ref in model
to.loadTopic_inModel()
return {
topicList,
to,
}
},
components: {
EELayoutMainLayout,
},
})
</script>
A few digressions before solving the problem. Maybe you are a java developer. I personally think it is inappropriate to write the front end with Java ideas. The design of vue3's setup is more inclined to combined functional programming
To fully understand why you need some pre knowledge, Proxy and the get and set method of Object
They correspond to the two core apis in vue, reactive and ref,
The former can only be applied to objects( because proxy can only proxy objects),The latter can be applied to any type(primary for basic javascript types, get and set can apply for any type)
You can modify the code to meet your expectations
loadTopic_inModel() {
super.loadData('topics', { t_type_id: 1 }).then((re) => {
console.log(re.data)
this.topicList.value = re.data
})
}
You cannot modify a ref object directly, a test case to explain what is reactive
when ref function is called, a will be like be wrapped in a class has value properties, and has get and set method
the effect function will call the arrow function, and in this time, the get method of a will be called and it will track as a dependence of the effect function, when a changed, the set method of a will be called, and it will trigger the arrow function,
so when you direct modify the a, the setter method will never trigger, the view will not update
const a = ref(1)
let dummy
let calls = 0
effect(() => {
calls++
dummy = a.value
})
expect(calls).toBe(1)
expect(dummy).toBe(1)
a.value = 2
expect(calls).toBe(2)
expect(dummy).toBe(2)
// same value should not trigger
a.value = 2
expect(calls).toBe(2)

Passing values between React components with useState

I use a React component with Apollo client as function component. The function body of my main search component looks like this:
function SearchContainer(props) {
const [begTime, setBegTime] = useState('')
const runSearch(begin_time) {
console.log('begin_time: ', begin_time) <== The printed value is Ok!
setBegTime(begin_time) <=== Use hook to set the value of begTime
console.log('begTime: ', begTime) <=== The output shows no change in begTime. Why?
}
return (
// Use SearchForm to get the search parameters.
<SearchForm
handleSearch={runSearch} <== Use SearchForm to get begin_time back into this component.
/>
// Provide the parameters from SearchForm and run the useQuery from Apollo using this parameters.
<SearchResults
begTime={begTime}
/>
)
}
The SearchForm is just a usual form as a React function component with useState hooks and calls on form submit the hanldeSearch function.
function SearchForm({handleSearch}) { <== handleSearch function from the parent component.
const handleSubmit = (begin_time) => {
handleSearch(begin_time) <== This call works and the value will be provided to the parent.
}
...
}
My idea of this code is to create 2 independent components. One component (SearchForm) should get the parameters. The other component (SearchResults) will get this parameters as arguments, run the query using useQuery and show the results.
But the useState hook does not work very well in my case. Interesting enough if I call the corresponding search form twice, I can see in the runSearch function, that the begTime has got the previous search values and not the initial value. So apparently the useState kind of works, but I want to run the search with the current values and not with the previous ones.
Is it possible at all to create such components with React hooks? It's my first big test with hooks instead of classes.
Thanks in advance
Andrej
For your question
const runSearch(begin_time) {
console.log('begin_time: ', begin_time)
setBegTime(begin_time)
console.log('begTime: ', begTime) <=== The output shows no change in begTime. Why?
}
The output shows no change in begTime. Why?
As stated in docs when we set state that is async function.
By that i mean your code will keep running and to set state react will start child process on another thread. And when its complete it will pass result to main thread. ( that you can c in useEffect or componentDidUpdate for early version).
So Main points are
at setBegTime(begin_time) async process is started
at main thread code will not wait for it
So next statement that is console.log('begTime: ', begTime) is processed and u saw no changes as in actual its value is not updated yet. React is still updating value.
Updating process is async because React dont want to main thread to wait for heavy work ( updating state is heavy process ) as if it wait then webpage will not respond untill it is completed. So it instead run that process on another thread.
For second one
you can try this
function SearchContainer(props) {
const [begTime, setBegTime] = useState('')
const [response,setResponse] = useState({})
useEffect(()=>{
const runSearch = (begin_time) {
setBegTime(begin_time)
}
},[begin_time])
// u can rather define working of handleSubmit in parent component and <br/>
// store its output in state and then pass it to another component
const handleSubmit = (begin_time) => {
resp = handleSearch(begin_time)
setResponse(resp)
}
return (
// Use SearchForm to get the search parameters.
<SearchForm
handleSearch={()=>handleSubmit()}
/>
// Provide the parameters from SearchForm and run the useQuery from Apollo using this parameters.
<SearchResults
begTime={begTime}
response={response}
/>
)
}

Apollo Client Error, Objects are not valid as a React child (found: [object Promise])

I'm getting a weird error when attempting to do async/await on my graphql query. Not sure what to do here.
import React, { Component } from "react";
import { ApolloConsumer } from 'react-apollo';
import Landing from '../modules/landing/index.js';
import getUser from '../shared/services/get-user';
export default class extends Component {
checkLoggedIn = async (client) => {
const response = await getUser(client);
console.log(response);
}
render() {
return (
<ApolloConsumer>
{client => (
<div>
{this.checkLoggedIn(client)}
<Landing />
</div>
)}
</ApolloConsumer>
)
}
}
If I remove the async await syntax, the app proceeds to execute my getUser query. However, when I try to do this with async/await. My app shows the error above. Is there something about ApolloConsumer that I do not understand?
The reason you are getting the error is because async function returns a promise. And, you are trying to render a promise inside of the render method. This isn't Apollo specific error, React simply doesn't allow this.
The reason why it works when you don't have the async there is because the function returns undefined which react treats as null in the render method and doesn't render anything.

Multiple Queries/Mutation in Apollo 2.1

I need some help using the new Query and Mutation component in Apollo 2.1, especially with multiple queries and mutations.
I have the following problems:
I have a graphql request that depends on a previous graphql result, how can I deal with this?
How do I add two different mutations (in my component I need to do two different actions) in a component that already has a query?
edit 2019/08/24
from the Apollo docs:
The new hooks API for Apollo Client is a simpler way to fetch data in
your React app without the boilerplate of render prop components and
higher-order components (HOC). We recommend using hooks for all new
Apollo code going forward.
original answer:
You are supposed to nest them. See this example:
const NumbersWithData = () => (
<Query query={QueryOne}>
{({ loading: loadingOne, data: { one } }) => (
<Query query={QueryTwo}>
{({ loading: loadingTwo, data: { two }}) => {
if (loadingOne || loadingTwo) return <span>loading...</span>
return <h3>{one} is less than {two}</h3>
}}
</Query>
)}
</Query>
);
To help with keeping the nesting manageable, you could check react-adopt. They have an Apollo ToDo App example, where they combine a Query and multiple Mutations.
For this purpose react-apollo exports a compose function. Using this function you may cleanly use several component enhancers at once. Including multiple graphql(), or even Redux connect() enhancers.
import { Mutation, compose, graphql } from "react-apollo";
class AddTweet extends Component {
....
....
....
}
export default compose(
graphql(GET_AUTHORS, { name: "getAuthors" }),
graphql(ADD_TWEET, { name: "addTweet" }),
connect(...), // incase you are using Redux
)(AddTweet);
An important note is that compose() executes the last enhancer first and works its way backwards through the list of enhancers.
One more thing lets say you were using this.props.data now you will get get undefined. just console.log(this.props) and you will see what is happening to props now. You will be having two properties now getAuthors and addTweet. So now it will be this.props.name-in-compose.name-of-type-in-typeDefs i.e. this.props.getAuthors.getUsers. It took me a bit to figure it out.
In my opinion,
To make a request depends on previous request, you can break that request to children component and pass result of previous request like props to it and do that request.
To use more than one mutation and queries, you can use compose like this
...
#compose(
graphql(GET_FEEDS_QUERY, {name : 'getFeeds'}),
graphql(CREATE_NEW_POST, {name: "createNewPost"}),
graphql(LIKE_POST_MUTATION, { name: "unlikePostMutation"}),
...
)
class HomeScreen extends Component {
...
}
I wrote a Medium Post about how to combine Mutation and Query on the same Component.
Here is a snippet from the post
// other import
import {Query} from “Apollo-graphql”; // new Query Component
import gql from "graphql-tag";
import { graphql } from "react-apollo";
import UserComponent from '../component/UserComponent'; // any component to display query result
const GET_ALL_USER = gql`
{
allUsers: {
firstname,
lastname,
username,
# other information
}
}
`
const UPDATE_USER_STATUS = gql`
mutation UpdateUserStatus($userID: ID!, $status: Int!){
updateUserState(userID: $userID, status: $status){
firstname,
lastname
username
# other information
}
}
`
ExampleComponent extends React.Component{
onEditInformation = async (user) => {
const response = await mutate({
variables: {userID: user}
})
}
render(){
return(
<Query query={GET_ALL_USER}>
{({data: { allUsers }}) => {
return allusers.map(user => {
return (
<UserComponent
user={user}
onEdit={() => this.onEditInformation(user)}
/>
)
})
}}
</Query>
)
}
}
export default graphql(UPDATE_USER_STATUS)(ExampleComponent);
Asides from using compose from react-apollo, another great utility library you can check it out is react-adopt. A great small utility lib that helps you to compose multiple render props type components so you don't have a nested hell patterns.
I have wrote a similar answer that basically covers all your current needs in terms of:
How to consume a previous result from your mapper fn via react-adopt
Combine multiple Query/Mutations from Composed component via react-adopt
Here's the detailed answer you're looking for & hopefully can be helpful to solving your problems :)
Best solution for this
Simply nest graphql function
export default graphql(addBookMutation)(graphql(getAuthorsQuery)(AddBook))
You can refer to this
Apollo concepts

Reducer for deep state

I have an initial state which looks like this (simplified for the purpose of this question):
export default {
anObject: {
parameters: {
param1:'Foo',
param2:'Bar'
},
someOtherProperty:'value'
}
};
And I have a reducer for anObject part of which deals with changes to parameter. I have an action which passed the id of the parameter to change, along with the newValue for that parameter. The reducer (again, very slightly simplified) looks like this:
import * as types from '../actions/actionTypes';
import initialState from './initialState';
export default function anObjectReducer(state = initialState.anObject, action){
switch(action.type){
case types.UPDATE_PARAMETER:
return Object.assign(
{},
state,
{
parameters:Object.assign(
{},
state.parameters,
{ [action.id]: action.newValue })
});
default:
return state;
}
}
This reducer looks wrong to me. I assumed I would be able to just do it like this:
case types.UPDATE_PARAMETER:
return Object.assign({},state,{parameters:{[action.id]:action.newValue}});
But this seems to wipe out all the other parameters and just update the single one being changed. Am I missing something obvious about how to structure my reducer?
In case it's relevant this is how I set up my root reducer:
import { combineReducers } from 'redux';
import anObject from './anObjectReducer';
export default combineReducers({
anObject
});
I thought there might be a way to compose reducers for the individual parts of each object - ie separately for parameters and someOtherProperty part of anObject in my example?
The reason why it wipes out other parameters is because you don't pass the previous values in the Object.assign.
You should have done that:
return Object.assign({}, state, {
parameters: Object.assign({}, { [action.id]: action.newValue }),
});
Or with the ES6 spread syntax: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator
return {
...state,
parameters: {
...state.parameters,
[action.id]: action.newValue,
}
}
You can:
restructure your reducers: you may use combineReducers not for store's root only. This way store stays the same as well as actions but reducers for nested object become lightweight
restructure your state(and reducers) to have it as flat as possible. it'd be more efforts needed but typically it also make easier to fetch data in mapStateToProps. normalizr should help to make transition easier by encapsulating integration with existing API that
requires specific data structure
use immer to write code like you're mutating state. This is definitely bad idea if you are learning React, but I'd consider using it on small real projects

Resources