I am trying to run a component method of another component. I am trying this using react ref. I am also following this link:
https://medium.freecodecamp.org/react-changing-state-of-child-component-from-parent-8ab547436271 But my structure is a bit more complicated.
List.js
class List extends Component {
constructor(){
super()
this.LoadCounterElement = React.createRef()
}
render(){
return(
<View>
<ItemGenerator />
<LoadCounter ref={this.LoadCounterElement}/>
</View>
)
}
}
function mapStateToProps(state) {
return {
counter: state.counter.counter
}
}
function mapDispatchToProps(dispatch) {
return {
increaseCounter: () => dispatch({ type: 'INCREASE_COUNTER' }),
decreaseCounter: () => dispatch({ type: 'DECREASE_COUNTER' }),
}
}
export default connect(mapStateToProps)(List);
ItemGenerator.js
class ItemGenerator extends Component {
render() {
return (
<ScrollView>
{
this.state.data.map((item, index) => {
return(<ItemList navigate={this.props.navigate} data={item} key={index}/>)
})
}
</ScrollView>
)
}
}
LoadCounter.js
class LoadCounter extends Component {
constructor(props) {
super(props)
this.state = {
count : 0,
}
}
componentDidMount() {
this._renderCount()
}
_renderCount = () => {
this.setState({count:this.props.counter})
}
render(){
return(
<View>
<Text>{this.state.count}</Text>
</View>
)
}
}
function mapStateToProps(state) {
return {
counter: state.counter.counter
}
}
export default connect(mapStateToProps)(withNavigation(LoadCounter));
ItemList.js
class ItemList extends Component {
render() {
return(
<View>
<TouchableOpacity onPress={() => {
this.props.increaseCounter()
this.LoadCounterElement.current._renderCount()
}}>
<Card containerStyle={{margin: 0}}>
<View style={{flex:1, flexDirection:'row', height:70, alignItems:'center', justifyContent:'space-between'}}>
<View style={{flexDirection:'row', alignItems:'center', width:'55%'}}>
<View style={{flexDirection:'column', marginLeft:10}}>
<Text style={{...}}>{this.props.data.name}</Text>
</View>
</View>
</View>
</Card>
</TouchableOpacity>
</View>
)
}
}
function mapDispatchToProps(dispatch) {
return {
increaseCounter: () => dispatch({ type: 'INCREASE_COUNTER' }),
decreaseCounter: () => dispatch({ type: 'DECREASE_COUNTER' }),
}
}
export default connect(mapStateToProps)(ItemList);
counterReducer.js
const initialState = {
counter: 1
}
const counterReducer = (state = initialState, action) => {
switch (action.type) {
case 'INCREASE_COUNTER':
return { counter: state.counter + 1 }
case 'DECREASE_COUNTER':
return { counter: state.counter - 1 }
}
return state
}
export default counterReducer;
As you can see in ItemLiist Component, i am trying to run _renderCount method which is in Component LoadCounter. But its not working. Kindly guide what i am missing?
The problem here is that you have some data in child component that should be reflected in a parent component. I would recommend that you move the shared state in the parent component or to the reducer state.
It is odd that you are using an action creator to increment/decrement counts - which I am thinking that updates some reducer state. If this is the case, why store that state in the local component state again ? You could just read the counter state from the reducer in your parent component.
Parent.js
class Parent extends React.Component {
render() {
return (
<div>
<span>{this.props.count}</span>
</div>
);
}
}
const mapStateToProps = state => ({
count: state.yourCountReducer.count,
});
export default connect(mapStateToProps)(Parent);
Child.js
class Child extends React.Component {
render() {
return (
<div>
<button onClick={() => this.props.increaseCounter()}>+</button>
<button onClick={() => this.props.decreaseCounter()}>-</button>
</div>
);
}
}
const mapDispatchToProps = dispatch => ({
increaseCounter: () => dispatch({ type: 'INCREASE_COUNTER' }),
decreaseCounter: () => dispatch({ type: 'DECREASE_COUNTER' }),
});
export default connect(null, mapDispatchToProps)(Child);
This way, the parent component will show the updated counter state when the child component updates the count. From your sample code, I am not sure if there is any good reason to store a shared reducer state in any component's local state.
Related
So I'm trying to display an array with some data in a Flatlist, I use React-Redux to update the initial State in the Reducer, I do it by collecting all data in LIST CREATION screen, and I use .push to insert them in an array, then I use updateList to update the initial State of list, and I use getDerivedStateFromProps to get that list in TRIALLIST screen and display it in a Flatlist, the problem is that I unintentionally created nested arrays, and it doesn't let me display the data in the Flatlist, this is the example of an array I'm trying to display:
Array [
Array [
Object {
"Name": Object {
"Name": "John",
},
"key": 0.05992611071666909,
"favNumber": 1,
"age": 30,
},
],
]
and here there are the various screens:
LIST CREATION
import { connect } from 'react-redux';
import { updateList } from '../../../../../redux/actions/index.js';
class trial extends Component {
constructor(props) {
super(props);
this.state = {
trial: '',
list: [],
};
}
submitTrial(){
let list = this.state.list;
list.push({
key: Math.random(),
Name: this.props.route.params,
favNum: favNum,
age: age,
});
this.props.updateList(list);
this.props.navigation.navigate("TrialList");
}
render() {
return (
<Button transparent>
<Icon
name="checkmark"
onPress={() => this.submitTrial()}
/>
</Button>
const mapDispatchToProps = { updateList };
export default connect( mapDispatchToProps )( trial );
TRIALLIST
class TrialList extends Component {
constructor(props) {
super(props);
this.state = {
list: [],
};
}
static getDerivedStateFromProps(props, state) {
if (props?.list) {
return {
list: [...state.list, props.list],
};
}
return null;
}
render() {
return (
<FlatList
data={this.state.list}
keyExtractor={(item, index) => item.key.toString()}
contentContainerStyle={{ flexGrow: 1 , flexGrow: hp('20%')}}
renderItem={({ item }) => (
<View>
<View>
<Text>
{item.Name.Name}
</Text>
<Text>
{item.favNumber}
</Text>
<Text>
{item.age}
</Text>
</View>
</View>
/>
function mapStateToProps(store){
return{
list: store.userState.list
};
}
export default connect(mapStateToProps)(TrialList);
INDEX.JS
import { ADD_LIST } from "../constants/index";
export const updateList = (list) => {
return console.log({ type: ADD_LIST, payload: list})
}
REDUCER
import { USER_STATE_CHANGE, ADD_LIST } from "../constants";
const initialState = {
currentUser: null,
list: null,
};
export const user = (state = initialState, action) => {
switch (action.type){
case USER_STATE_CHANGE:
return {
...state,
currentUser: action.currentUser,
};
case ADD_LIST:
return{
...state,
list: [...action.payload],
}
default:
return state
}
};
Thanks in advance for your help.
I am trying to learn how to connect APIs in React Native. I am using a sample API: https://reactnative.dev/movies.json
This is my code:
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
loading: true,
dataSource: [],
};
}
componentDidMount() {
return fetch("https://reactnative.dev/movies.json")
.then((response) => response.json())
.then((responseJson) => {
this.setState({
loading: false,
dataSource: responseJson.movies,
});
})
.catch((error) => console.log(error)); //to catch the errors if any
}
render() {
if (this.state.isLoading) {
return (
<View style={styles.container}>
<ActivityIndicator size="large" color="#0c9" />
</View>
);
} else {
let products = this.state.dataSource.map((val, key) => {
return (
<View key={key} style={styles.item}>
<Text>{val}</Text>
</View>
);
});
return (
<View style={styles.container}>
<Text>{products.title}</Text>
</View>
);
}
}
}
The problem occurs with my "products" variable. In debug mode, I was able to see the key and value pairs which were correct from the API. However, the products array is populated with objects rather than strings which are structured like this:
Object {$$typeof: Symbol(react.element), type: "RCTView", key: "0", …}
My code returns the following error: this.state.dataSource.map is not a function
EDIT:
The answer below worked for the API I was using. Now I am trying a different API structured like this:
{"prods":
{
"86400":{"slug":"86400","url":"/86400"},
"23andme":{"slug":"23andme","url":"/23andme"}
}}
I am having trouble with the mapping again. This returns an error:
return dataSource.map((val, key) => (
<View key={key} style={styles.item}>
<Text>{val.slug}</Text>
</View>
));
First, there is a small typo in your example. In your component's constructor you specify a loading state variable, but in your render function you're using isLoading. Second, you're not mapping over your data correctly. It just looks like you need to specify what aspects of each movie you care about in your render function. JSX can't handle displaying a full javascript object which is what <Text>{val}</Text> ends up being in your code. There are a few ways you can fix this. It's very common to just map over your results and display them directly.
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
loading: true,
dataSource: []
};
}
componentDidMount() {
return fetch("https://reactnative.dev/movies.json")
.then(response => response.json())
.then(responseJson => {
this.setState({
loading: false,
dataSource: responseJson.movies
});
})
.catch(error => console.log(error));
}
render() {
const { loading, dataSource } = this.state;
if (loading) {
return (
<View style={styles.container}>
<ActivityIndicator size="large" color="#0c9" />
</View>
);
}
return dataSource.map((movie, index) => (
<View key={movie.id} style={styles.item}>
<Text>{movie.title}</Text>
</View>
));
}
}
You could also pull this out to a renderMovies method, which might help since you are trying to display these in a styled container.
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
loading: true,
dataSource: []
};
}
componentDidMount() {
return fetch("https://reactnative.dev/movies.json")
.then(response => response.json())
.then(responseJson => {
this.setState({
loading: false,
dataSource: responseJson.movies
});
})
.catch(error => console.log(error));
}
renderMovies() {
const { dataSource } = this.state;
return dataSource.map((movie, index) => (
<View key={movie.id} style={styles.item}>
<Text>{movie.title}</Text>
</View>
));
}
render() {
const { loading } = this.state;
if (loading) {
return (
<View style={styles.container}>
<ActivityIndicator size="large" color="#0c9" />
</View>
);
}
return (
<View style={styles.container}>
{this.renderMovies()}
</View>
);
}
}
I have used Object.values() to restructure the object into an array
componentDidMount() {
return fetch("https://reactnative.dev/movies.json")
.then((response) => response.json())
.then((responseJson) => {
this.setState({
loading: false,
dataSource: Object.values(responseJson.movies), //changed this
});
})
.catch((error) => console.log(error));
}
Try simple way. This code uses modern React practice and helps you to brush up your React skills in general. Give a try.
import React, {useState, useEffect} from 'react';
import { Text, View, StyleSheet } from 'react-native';
import axios from 'axios'; //for fetching data
export default function App() {
//React Hook for state
const [ data, setData ] = useState ([]);
//React Hook Instead Of ComponentDidMount
useEffect(() => {
const fetchData = async () => {
const result = await axios(
"https://reactnative.dev/movies.json",
);
setData(result.data.movies);
};
fetchData();
}, []);
return (
<View>
<Text>{JSON.stringify(data)}</Text>
</View>
);
}
I am trying to get a specific checkbox to check and uncheck.
This is the parent component:
import React, { Component } from 'react';
import { View, Text, Modal, TouchableHighlight } from 'react-native'
import { loadLeagues } from '../actions'
import { connect } from 'react-redux'
import Check from './CheckBox'
class LeagueSelect extends Component {
constructor(props) {
super(props)
this.state = {
modalVisible: false,
checked: []
}
}
// state = {
// modalVisible: false,
// checked: []
// }
setModalVisible(visible) {
this.setState({modalVisible: visible})
this.props.league.map(
(v, i) => {
return this.state.checked.push(false)
}
)
}
componentDidMount() {
this.props.loadLeagues()
}
changeCheck = (index) => {
newChecked = this.state.checked.splice(index, 1, !this.state.checked[index])
console.log('newChecked', this.state.checked)
this.setState({ checked: newChecked })
console.log('league checked state', this.state.checked)
}
render() {
return (
<View style={{marginTop: 22}}>
<Modal
animationType="slide"
transparent={false}
visible={this.state.modalVisible}
onRequestClose={() => {
Alert.alert('Modal has been closed.');
}}
>
<View style={{marginTop: 100}}>
<TouchableHighlight
onPress={() => {
this.setModalVisible(!this.state.modalVisible);
}}
>
<Text>Hide Modal</Text>
</TouchableHighlight>
<Text>Leagues</Text>
{this.props.league === null ?'' : this.props.league.map(
(v, i) => {
return(
<View>
<Check
checked={this.state.checked[i]}
index={i}
changeCheck={this.changeCheck}
/>
<Text>{v.acronym}</Text>
</View>
)
}
)}
</View>
</Modal>
<TouchableHighlight
onPress={() => {
this.setModalVisible(true);
}}>
<Text>Show Modal</Text>
</TouchableHighlight>
</View>
);
}
}
function mapStateToProps(state) {
return {
league: state.league.league
}
}
export default connect(mapStateToProps, { loadLeagues })(LeagueSelect)
This is the child component that I'm passing the index of the the checkbox into to update; I'm also passing in the function that updates the state of the checkbox at the specific index:
import React, { Component } from 'react';
import { Text, View } from 'react-native';
import { CheckBox } from 'native-base'
export default class Check extends Component {
constructor(props) {
super(props)
this.state = {
cards: []
}
}
localChange = () => {
this.props.changeCheck(this.props.index)
}
render() {
return(
<CheckBox
checked={this.props.checked}
onPress={this.localChange}
/>
)
}
}
I can see that the state updates when press the checkbox through the console logs that I have setup, but the checkbox isn't updating based off of the new state.
I tried to fetch the data from function inside render, when I use alert() it works, but when I try to return real text it not shown any thing
the code I work with:
export default class App extends Component {
renderResualts(){
db.find({}, function (err, docs) {
return docs.map(function(d){
return(
<Text>{d.name}</Text>
)
})
})
}
render() {
return (
<View style={styles.container}>
{ this.renderResualts() }
</View>
);
}
}
--
UPDATED CODE:
export default class App extends Component {
constructor(props){
super(props)
this.state = {graves:[]}
}
componentDidMount(){
db.find({}, function (err, docs) {
this.setState({graves:docs})
})
}
renderResults(){
return this.state.graves.map(grave =>
<Text>{grave.name}</Text>
)
}
render() {
return (
<View style={styles.container}>
{ this.renderResults() }
</View>
);
}
}
It should be shown these data :
{
_id: 1,
name: 'Parturient',
year: 2017
}, {
_id: 2,
name: 'Dapibus',
year: 2017
}
inside text like so: ParturientDapibus
try this
renderResults(){
return (
<View>
this.state.graves.map(grave =>
<Text>{grave.name}</Text>
)
</View>
);
}
By passing the data to State like so:
export default class App extends Component {
constructor(props){
super(props)
this.state = {graves:[]}
}
componentDidMount(){
db.find({}, (err, docs)=> {
this.setState({graves:docs})
})
}
renderResults(){
return this.state.graves.map(grave =>
<Text key={grave._id}>{grave.name}</Text>
)
}
render() {
return (
<View style={styles.container}>
{ this.renderResults() }
</View>
);
}
}
I create a Component which is getting data from server then shows data on the screen. The screen look good when I use react-native views directly.
After that, I refactor the code to make it reusable by moving a snippet of code to a new component (TwoGroupItemsView):
export class MainComponent extends Component {
componentDidMount() {
return fetch('https://mycompany.com/items')
.then(res => res.json())
.then(json => {
this.setState({
isLoading: false,
items: json.data,
}, function(){
});
}
}
render() {
return (
<View>
<View>
{
this.state.items != null &&
<TwoGroupItemsView title={'Group 1'} items={this.state.items}/>
}
</View>
</View>
);
}
}
class TwoGroupItemsView extends View {
constructor(props) {
super(props);
}
render() {
return (
<View style={{marginTop: 16}}>
//... FlatList for items array
</View>
)
}
}
I always get:
TypeError: null is not an object
When evaluating 'this.state.items'.
Can you show me the way to create my own reusable views?
Your state is being set asynchronously. Try explicitly initializing it before the Promise resolves. Some possibilities below.
Declaring the initial state:
export class MainComponent extends Component {
state = {
isLoading: true, // sample values
items: null
}
Or setting in the constructor:
export class MainComponent extends Component {
constructor(props) {
super(props);
this.state = {
isLoading: true, // sample values
items: null
};
}
Or strengthening the guards:
render() {
return (
<View>
<View>
{
this.state && this.state.items != null &&
<TwoGroupItemsView title={'Group 1'} items={this.state && this.state.items}/>
}
</View>
</View>
);
}
Re write your render function.
render() {
return (
<View>
<View>
{
this.state.items ?
<TwoGroupItemsView title={'Group 1'} items={this.state.items}/> : null
}
</View>
</View>
);
}