I have three buttons. When I click one button, it fires Context's memo function to add an item into Context's items. I see that clicking one button re-renders all the three buttons, which is not expected. I need to re-render only the clicked button. Please advice.
CounterContext.js
import { createContext } from "react";
export const CounterContext = createContext(null);
App.js
import React from "react";
import { Text, StyleSheet } from "react-native";
import MyButton from "./MyButton";
import { CounterContext } from "./CounterContext";
function counterReducer(prevState, action) {
switch (action.type) {
case "ADD_ITEM":
let items = [...prevState.items, action.item];
return {
items: items,
count: items.length
};
default:
console.log("No action type");
break;
}
}
const buttons = ["1", "2", "3"];
export default function App() {
const [counterState, counterDispatch] = React.useReducer(counterReducer, {
items: [],
count: 0
});
const counterContext = React.useMemo(
() => ({
addItem: item => {
counterDispatch({
type: "ADD_ITEM",
item: item
});
},
items: counterState.items,
itemsCount: counterState.items.length
}),
[counterState.items]
);
return (
<CounterContext.Provider value={counterContext}>
{buttons.map((button, index) => {
return <MyButton key={index} id={button} />;
})}
<Text style={styles.counter}>{counterContext.itemsCount}</Text>
<Text style={styles.items}>{JSON.stringify(counterContext.items)}</Text>
</CounterContext.Provider>
);
}
const styles = StyleSheet.create({
counter: {
margin: 10,
fontSize: 30
},
items: {
margin: 10,
fontSize: 18
}
});
MyButton.js
import React from "react";
import { TouchableOpacity, Text, StyleSheet } from "react-native";
import { CounterContext } from "./CounterContext";
export default function MyButton(props) {
const { addItem } = React.useContext(CounterContext);
let time = new Date().toLocaleTimeString();
console.log(time + " - Rendering MyButton #" + props.id + " ...");
return (
<TouchableOpacity
style={styles.myButton}
onPress={() => {
addItem({
name: "Item #" + props.id
});
}}
>
<Text>Add</Text>
</TouchableOpacity>
);
}
const styles = StyleSheet.create({
myButton: {
width: 100,
margin: 10,
padding: 10,
textAlign: "center",
borderWidth: 1,
borderColor: "#ccc",
backgroundColor: "#e0e0e0",
borderRadius: 5
}
});
Here is the Codesandbox codes: https://codesandbox.io/s/usecontext-dependency-fpcep?file=/src/App.js
Turns out that I have to split Context into two, one that doesn’t have dependency, only to run the functions, and one that shows any value(s) real-time by adding the dependency. The same with the consumer component(s), take it out as new component and put the useContext in there, so parent component won’t be re-rendered.
Related
New to react native. I want to render what is currently being logged in the console into a separate jsx Text elements, but I've been having trouble.
const handleSubmit = async () => {
const response = await fetch(
`http://127.0.0.1:5000/GetRecipes/${searchText}`,
{
method: "GET",
}
);
const result = await response.json();
const recipes = result["recs"];
for (var meal in recipes) {
if (recipes.hasOwnProperty(meal)) {
console.log(recipes[meal].meal); // want to display this
setSubmitPressed(submitPressed);
}
}
// want to render the results in text elements
const Test = () => {
return <Text>hi</Text>;
};
const DisplayRecipes = ({ recipes }) => {
return (
<View>
<Text>Meals</Text>
{recipes.map((ing) => (
<View key={ing[meal].meal}>
<Text>{ing[meal].meal}</Text>
</View>
))}
</View>
);
};
};
The results should only be displayed after a user submits input from a text input field and the results are fetched.
HandleSubmit is called in the onPress prop of a button.
The reference data looks like this
{0, {“meal”: “Chicken Wings”}}, {1, {“meal”: “Hamburger”}}
Please find below code:
import * as React from 'react';
import { Text, View, StyleSheet } from 'react-native';
import Constants from 'expo-constants';
// You can import from local files
import AssetExample from './components/AssetExample';
// or any pure javascript modules available in npm
import { Card } from 'react-native-paper';
export default function App() {
const recipes = [
{
"id": 0,
"meal": "ChickenWings"
},
{
"id": 1,
"meal": "Hamburger"
}
]
return (
<View style={styles.container}>
<Text>Meals</Text>
{recipes.map((ing) => (
<View key={ing.id}>
<Text>{ing.meal}</Text>
</View>
))}
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
padding: 8,
},
});
You can set your data as per requirement.
I have an array with some phrases that looks like this:
words = [
{ text: '...', key: 1},
{ text: '...', key: 2},
{ text: '...', key: 3},
{ text: '...', key: 4} ... ]
I want to display a random text from this array when I click a button. This is the code that I have so far:
import { StatusBar } from 'expo-status-bar';
import React, { useState } from 'react';
import { StyleSheet, Text, View, Button } from 'react-native';
export default function App() {
var textValue = 'Change me'
words = [...]
const len = words.length;
const changeTextValue = () => {
textValue = words[Math.floor(Math.random() * len)].text
}
return (
<View style={styles.container}>
<Text>{textValue}</Text>
<Button onPress={changeTextValue} title='Press me'/>
<StatusBar style="auto" />
</View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: 'lightblue',
alignItems: 'center',
justifyContent: 'center',
},
});
But it doesn't work. Could you please help me with this? Thanks in advance.
Put it into state and display that state. Change state when button is clicked:
import { StatusBar } from 'expo-status-bar';
import React, { useState } from 'react';
import { StyleSheet, Text, View, Button } from 'react-native';
export default function App() {
const [textValue, setTextValue] = useState('');
words = [...]
const changeTextValue = () => {
const len = words.length;
setTextValue(words[Math.floor(Math.random() * len)].text)
}
return (
<View style={styles.container}>
<Text>{textValue}</Text>
<Button onPress={changeTextValue} title='Press me'/>
<StatusBar style="auto" />
</View>
)
}
In order to save your local state in React you need useState Hook. Hooks are just functions that you use in order to "hook into" internal React mechanims.
Your example doesn't work because, this App Functional Component is rerendered, meaning that this function is called every time something changes, and text value that you have reset to "change me" on every render.
What is more, since the array of your words is constant, there is no need to put it inside functional component since, it will be redeclared on every render. It's better to move it outside of your component.
To make it work, you need to do it following way:
import {
StatusBar
} from 'expo-status-bar';
import React, {
useState
} from 'react';
import {
StyleSheet,
Text,
View,
Button
} from 'react-native';
const words = [{
text: '...',
key: 1
},
{
text: '...',
key: 2
},
{
text: '...',
key: 3
},
{
text: '...',
key: 4
}...
]; // we move it outside so it's not redeclared
export default function App() {
const [textValue, setTextValue] = useState('change me'); // we call use state hook with initial value
const len = words.length;
const changeTextValue = () => {
setTextValue(words[Math.floor(Math.random() * len)].text) // we use setTextValue function that the hook returned for us
}
return ( <
View style = {
styles.container
} >
<
Text > {
textValue
} < /Text> <
Button onPress = {
changeTextValue
}
title = 'Press me' / >
<
StatusBar style = "auto" / >
<
/View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: 'lightblue',
alignItems: 'center',
justifyContent: 'center',
},
});
The intended function is that it would just delete the one goal that is clicked but for some odd reason it decides onPress to delete all goals listed.
I am following this tutorial https://www.youtube.com/watch?v=qSRrxpdMpVc and im stuck around 2:44:45. If anyone else has done this tutorial and or can see my problem an explanation would be greatly appreciated. :)
Program
import React, { useState } from "react";
import {
StyleSheet,
Text,
View,
Button,
TextInput,
ScrollView,
FlatList
} from "react-native";
import GoalItem from "./components/GoalItem";
import GoalInput from "./components/GoalInput";
export default function App() {
const [courseGoals, setCourseGoals] = useState([]);
const addGoalHandler = goalTitle => {
setCourseGoals(currentGoals => [
...currentGoals,
{ key: Math.random().toString(), value: goalTitle }
]);
};
const removeGoalHander = goalId => {
setCourseGoals(currentGoals => {
return currentGoals.filter((goal) => goal.id !== goalId);
});
};
return (
<View style={styles.screen}>
<GoalInput onAddGoal={addGoalHandler} />
<FlatList
keyExtractor={(item, index) => item.id}
data={courseGoals}
renderItem={itemData => (
<GoalItem
id={itemData.item.id}
onDelete={removeGoalHander}
title={itemData.item.value}
/>
)}
></FlatList>
</View>
);
}
const styles = StyleSheet.create({
screen: {
padding: 80
}
});
Function
import React from "react";
import { View, Text, StyleSheet, TouchableOpacity } from "react-native";
const GoalItem = props => {
return (
<TouchableOpacity onPress={props.onDelete.bind(this, props.id)}>
<View style={styles.listItem}>
<Text>{props.title}</Text>
</View>
</TouchableOpacity>
);
};
const styles = StyleSheet.create({
listItem: {
padding: 10,
backgroundColor: "lightgrey",
borderColor: "grey",
borderRadius: 5,
borderWidth: 1,
marginVertical: 10
}
});
export default GoalItem;
When updating state, you have to pass new array so component can detect changes and update view
const removeGoalHander = goalId => {
setCourseGoals(currentGoals => {
const newGoals = currentGoals.filter((goal) => goal.id !== goalId);
return [...newGoals];
});
};
I am creating a component that animates a group of buttons in an elastic staggered way using react-native-pose. The buttons them selves use pose to animate the pressed state. I have almost got it looking how I want it although I'm not sure I'm doing things correctly.
This is what I want to achhieve...
... However the text looks awful like its got jpg artefacts on it :
App.js
import React, { Component } from 'react';
import styled from 'styled-components';
import posed from 'react-native-pose';
import Button from './src/components/Button';
const ScreenContainer = styled.View({
flex: 1,
padding: 20,
marginTop: 100
});
const Buttons = posed.View({
visible: {
staggerChildren: 100
},
hidden: {
staggerChildren: 100
}
});
export default class App extends Component {
state = {
buttonPose: 'hidden'
};
items = [
{ id: 0, label: 'One' },
{ id: 1, label: 'Two' },
{ id: 2, label: 'Three' }
];
componentDidMount = () => {
this.setState({
buttonPose: 'visible'
});
};
render() {
return (
<ScreenContainer>
<Buttons pose={this.state.buttonPose}>
{this.items.map(item => (
<Button label={item.label} key={item.id} />
))}
</Buttons>
</ScreenContainer>
);
}
}
Button.js
import React, { PureComponent } from 'react';
import { TouchableWithoutFeedback } from 'react-native';
import styled from 'styled-components';
import posed from 'react-native-pose';
const Container = styled(
posed.View({
visible: {
opacity: 1,
x: 0
},
hidden: {
opacity: 0,
x: -100
}
})
)({
marginBottom: 20
});
const Background = styled(
posed.View({
// If I comment out these poses the problem goes away
pressIn: {
scale: 1.1
},
pressOut: {
scale: 1
}
})
)({
padding: 20,
backgroundColor: '#f9415d',
borderRadius: 10
});
const Label = styled.Text({
fontSize: 18,
color: 'white',
textAlign: 'center'
});
export default class Button extends PureComponent {
state = {
buttonPose: 'pressOut'
};
onPressIn = () => {
this.setState({
buttonPose: 'pressIn'
});
};
onPressOut = () => {
this.setState({
buttonPose: 'pressOut'
});
};
componentDidMount = () => {};
render() {
const { onPressIn, onPressOut } = this;
const { buttonPose } = this.state;
const { label } = this.props;
return (
<Container>
<TouchableWithoutFeedback onPressIn={onPressIn} onPressOut={onPressOut}>
<Background pose={buttonPose} withParent={false}>
<Label>{label}</Label>
</Background>
</TouchableWithoutFeedback>
</Container>
);
}
}
Can anyone offer any insight into why the text and also the rounded corners look so artifacted and low res?
I am new at React Native.
I'm working on a code that will change the background color of a box (card) according to the data that I will get from API. I want to check first if the title is like 'Taylor' make background red , if it is 'Fearless' make it green and so on.
Here is the API that I got information from :
http://rallycoding.herokuapp.com/api/music_albums
This is the code divided into several files.
First of them index.js
// Import a library to help to create a component
import React from 'react';
import { Text, AppRegistry, View } from 'react-native';
import Header from './src/components/header.js';
import AlbumList from './src/components/AlbumList.js'
// create a component
const App = () => (
<View>
<Header headerText={'Smart Parking'}/>
<AlbumList />
</View>
);
//render it to the device
AppRegistry.registerComponent('albums2', () => App);
second is AlbumList.js
import React, { Component } from 'react';
import { View } from 'react-native';
import axios from 'axios';
import AlbumDetail from './AlbumDetail.js'
class AlbumList extends Component {
state = { albums: [] };
componentWillMount() {
axios.get('https://rallycoding.herokuapp.com/api/music_albums')
.then(response => this.setState({ albums: response.data }) );
}
renderAlbums() {
return this.state.albums.map(album =>
<AlbumDetail key={album.title} album={album} />
);
}
render() {
return(
<View>
{this.renderAlbums()}
</View>
);
}
}
export default AlbumList;
3rd is AlbumDetail.js
import React from 'react';
import {Text, View} from 'react-native';
import Card from './Card.js'
const AlbumDetail = (props) => {
return(
<Card>
<Text> {props.album.title} </Text>
</Card>
);
};
export default AlbumDetail;
4th is card which I need to change background of it
import React from 'react';
import { View } from 'react-native';
const Card = (props) => {
return (
<View style={styles.containerStyle}>
{props.children}
</View>
);
};
const styles = {
containerStyle:{
borderWidth: 1,
borderRadius: 2,
backgroundColor: '#ddd',
borderBottomWidth: 0,
shadowColor: '#000',
shadowOffset: {width: 0, height:2 },
shadowOpacity: 0.1,
shadowRadius: 2,
elevation: 1,
marginLeft: 5,
marginRight: 5,
marginTop: 10
}
};
export default Card;
last one is header
// Import libraries for making components
import React from 'react';
import { Text, View } from 'react-native';
// make a components
const Header = (props) => {
const { textStyle, viewStyle } = styles;
return(
<View style={viewStyle}>
<Text style={textStyle}>{props.headerText}</Text>
</View>
)
};
const styles ={
viewStyle:{
backgroundColor:'orange',
justifyContent: 'center',
alignItems: 'center',
height: 60,
},
textStyle: {
fontSize: 20
}
};
// make the component to the other part of the app
export default Header;
Basically you need to pass the title of the album as prop to the Card from the AlbumDetails component and then on each Card calculate the color to use and pass it in the style like this:
// AlbumDetails.js component
import React from 'react';
import {Text, View} from 'react-native';
import Card from './Card.js'
const AlbumDetail = (props) => {
return(
<Card title={props.album.title}>
<Text> {props.album.title} </Text>
</Card>
);
};
export default AlbumDetail;
// Card.js component
import React from "react";
import { View } from "react-native";
function calculateColor(title) {
let bgColor;
switch (title) {
case "Taylor":
bgColor = "red";
break;
case "Fearless":
bgColor = "green";
break;
default:
bgColor = "orange";
break;
}
return bgColor;
}
const Card = props => {
const { title } = props;
const backgroundColor = calculateColor(title);
return (
<View style={[styles.containerStyle, { backgroundColor: backgroundColor }]}>
{props.children}
</View>
);
};
const styles = {
containerStyle: {
borderWidth: 1,
borderRadius: 2,
backgroundColor: "#ddd",
borderBottomWidth: 0,
shadowColor: "#000",
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 2,
elevation: 1,
marginLeft: 5,
marginRight: 5,
marginTop: 10
}
};
export default Card;
Something like this should work:
Change the AlbumDetail to conditionally render the Card.
const AlbumDetail = props => {
if (props.album.title === 'Taylor') {
return (
<Card style={{ backgroundColor: 'red' }}>
<Text>{props.album.title}</Text>
</Card>
);
} else {
return (
<Card style={{ backgroundColor: 'green' }}>
<Text>{props.album.title}</Text>
</Card>
);
}
};
Override the default style of the card using the passed style prop.
const Card = props => {
return (
<View style={[styles.containerStyle, props.style]}>{props.children}</View>
);
};
accepted answer is great just its a bad habit to do things in render.
i also not sure since every title has a color why wouldnt the server send this in the object props in first place ? :)
class AlbumList extends Component {
state = { albums: [] };
componentDidMount() {
axios.get('https://rallycoding.herokuapp.com/api/music_albums')
.then(response=> Array.isArray(response.data) ? response.data : []) // alittle validation would not hurt :) !
.then(data => this.setState({ albums: data }) );
}
selectHeaderColorForAlbum( album ){
let headerColor = 'red';
// switch(album.title){ ..you logic }
return headerColor;
}
renderAlbums() {
return this.state.albums.map(album =>
<AlbumDetail key={album.title} album={album} color={this.selectHeaderColorForAlbum(album)} />
);
}
render() {
return(
<View>
{this.renderAlbums()}
</View>
);
}
}
const Card = (props) => {
return (
<View style={[styles.containerStyle,{color:props.headerColor}]}>
{props.children}
</View>
);
};
this is easier your logic will render only once.
also notice that react >16.0 depricated componentWillMount, so use DidMount