How do i handle HOC from it's wrapped component? - javascript

I have a HOC to handling loading using axios,
here's a code of withAxiosHOC:
export default (url, WrapComponent) => {
return class extends React.Component {
constructor(props) {
super(props);
this.state = {
data: null,
isLoading:false,
isFailed:false,
isError:false,
message:null,
};
}
componentDidMount(){
this.callAPI()
}
async callAPI(){
//show loading
// handle API and pass to wrapped component
// hide loading
}
render() {
if (this.state.isLoading) {
return (
//show loading UI
)
} else if(this.state.isFailed){
return(
//show Failed UI
)
} else if(this.state.isError){
return(
//show Error UI
)
}
return (
<WrapComponent data={this.state.data} {...this.props} />
)
}
}
}
and usually i'm used HOC like this, let say Home.js:
export default withAxiosHttp(
'https://reactnative.dev/movies.json',
class Home extends React.Component{
constructor(props) {
super(props);
this.state = {
data:props.data
}
}
render() {
return(
<View style={{flex:1, backgroundColor:Color.black}}>
<MyText>{JSON.stringify(this.state.data, null, 2)}</MyText>
</View>
)
}
}
)
but sometimes i need to call the URL depend on state of my wrapped component,
something like this Suggestion.js:
export default withAxiosHttp(
'https://exampleAPIneedDynamicValue.com/suggestion?lat='+this.state.position.lat+'&long='+this.state.position.long,
class Suggestion extends React.Component{
constructor(props) {
super(props);
this.state = {
data:props.data,
position:{lat:null, long:null}
}
}
componentDidMount(){
let tempPosition = this.state.position
tempPosition.lat = MyLatitude
tempPosition.long = MyLongitude
this.setState({position:tempPosition})
}
render() {
return(
<View style={{flex:1, backgroundColor:Color.black}}>
<MyText>{JSON.stringify(this.state.data, null, 2)}</MyText>
</View>
)
}
}
)
as you see in Suggestion.js, i need to call a URL depending on lat and long of position state,
and lat long state only available in wrappedComponent of HOC,
My Question:
How do i handle HOC to run when lat long state is available in wrappedComponent?
does my HOC can be used to POST method also?
Please give me a suggestion/answer in React Native scope

You can modify the url parameter is a function instead of a string.
In withAxiosHOC
async callAPI(){
axios.get(url(this.state)).then(data => {
//logic here
})
}
Your Suggestion.js will be
export default withAxiosHttp(
(state) => {
return 'https://exampleAPIneedDynamicValue.com/suggestion?lat='+state.position.lat+'&long='+state.position.long
},
class Suggestion extends React.Component{
constructor(props) {
super(props);
this.state = {
data:props.data,
position:{lat:null, long:null}
}
}
componentDidMount(){
let tempPosition = this.state.position
tempPosition.lat = MyLatitude
tempPosition.long = MyLongitude
this.setState({position:tempPosition})
}
render() {
return(
<View style={{flex:1, backgroundColor:Color.black}}>
<MyText>{JSON.stringify(this.state.data, null, 2)}</MyText>
</View>
)
}
}
)

Related

Updating parent state from child components not working in reactjs

I was going through react official documentation when I struck upon an example which updates the parent component through child component callbacks. I was able to understand how the flow works. However, when I tried to optimize the code further it failed to update the component via callbacks.
The Original Code:
https://codepen.io/gaearon/pen/QKzAgB?editors=0010
My code change:
class LoginControl extends React.Component {
constructor(props) {
super(props);
this.handleLoginClick = this.handleLoginClick.bind(this);
this.handleLogoutClick = this.handleLogoutClick.bind(this);
this.state = {isLoggedIn: false};
this.button = <MyButton message="Login" onClick={this.handleLoginClick} />;
}
handleLoginClick() {
this.setState({isLoggedIn: true});
}
handleLogoutClick() {
this.setState({isLoggedIn: false});
}
render() {
const isLoggedIn = this.state.isLoggedIn;
if (isLoggedIn) {
this.button = <MyButton message="Logout" onClick={this.handleLogoutClick} />;
} else {
this.button = <MyButton message="Login" onClick={this.handleLoginClick} />;
}
return (
<div>
<Greeting isLoggedIn={isLoggedIn} />
{this.button}
</div>
);
}
}
function UserGreeting(props) {
return <h1>Welcome back!</h1>;
}
function GuestGreeting(props) {
return <h1>Please sign up.</h1>;
}
function Greeting(props) {
const isLoggedIn = props.isLoggedIn;
if (isLoggedIn) {
return <UserGreeting />;
}
return <GuestGreeting />;
}
class MyButton extends React.Component {
constructor(props) {
super(props);
this.message=props.message;
this.click=props.onClick;
}
render() {
return (
<button onClick={this.click}>
{this.message}
</button>
);
}
}
ReactDOM.render(
<LoginControl />,
document.getElementById('root')
);
Ok the main problem here is that you are trying to assign to many things to "this".
React does not track changes and re-renders when component's method or properties changes.
try to avoid this pattern and use state and props directly.
Only changes to state or props will cause a component to re-render.
In you situation you can look at this code:
class LoginControl extends React.Component {
state = {isLoggedIn : false}
handleLoginClick = () => {
this.setState({isLoggedIn: true});
}
handleLogoutClick = () => {
this.setState({isLoggedIn: false});
}
button = () => {
const message = this.state.isLoggedIn ? "Logout" : "Login";
const onClick = this.state.isLoggedIn ? this.handleLogoutClick : this.handleLoginClick;
return <MyButton message={message} onClick={onClick} />
}
render() {
return (
<div>
<Greeting isLoggedIn={this.state.isLoggedIn} />
{this.button()}
</div>
);
}
}
function UserGreeting(props) {
return <h1>Welcome back!</h1>;
}
function GuestGreeting(props) {
return <h1>Please sign up.</h1>;
}
function Greeting(props) {
const isLoggedIn = props.isLoggedIn;
if (isLoggedIn) {
return <UserGreeting />;
}
return <GuestGreeting />;
}
class MyButton extends React.Component {
constructor(props) {
super(props);
this.message=props.message;
this.click=props.onClick;
}
render() {
return (
<button onClick={this.props.onClick}>
{this.props.message}
</button>
);
}
}
ReactDOM.render(
<LoginControl />,
document.getElementById('root')
);

The proper way of exchanging objects between sibling components in React

I am trying to build a simple React app and stuck into trouble. I have the following structure:
App.js
class App extends React.Component {
render() {
return (
<div className="App">
<PlanBuilder />
<PlanMenu />
</div>
);
}
}
PlanMenu.js
class PlanMenu extends React.Component {
render() {
return (
<div className="PlanMenu">
<button type="button"
onClick={addObject(
new CWall({
x: 100,
y: 100,
length: 200
}))}>Wall
</button>
</div>
);
}
}
PlanBuilder.js
class PlanBuilder extends React.Component {
constructor(props) {
super(props);
this.state = {
objects: []
};
}
addObject(object) {
this.setState({
objects: [...this.state.objects, object]
});
}
render() {
return (
<Stage>
<Layer>
{
this.state.objects.map(function(object) {
return object.render();
})
}
</Layer>
</Stage>
);
}
So the main idea is that I have two sibling components: the drawing area and the menu. When the button on the menu is pressed, I want to create a new object and send it to the drawing area element. So, the question is how to pass PlanBuilder.addObject method to the PlanMenu class. I came from the C world, and what I think about is to pass kinda function pointer to PlanMenu. However, I am not sure this is an appropriate solution. Would you please recommend me the proper way of doing this in React? Thank you in advance.
In this case you have two ways.
The simpler one is to move the logic you have on PlanBuilder to App, and pass the necessary props to PlanBuilder and PlanMenu, like:
class PlanMenu extends React.Component {
render() {
const { addObject } = this.props
return (
<div className="PlanMenu">
<button type="button"
onClick={addObject(
new CWall({
x: 100,
y: 100,
length: 200
}))}>Wall
</button>
</div>
);
}
}
class PlanBuilder extends React.Component {
render() {
const { objects } = this.props
return (
<Stage>
<Layer>
{objects.map(function(object) {
return object.render();
})}
</Layer>
</Stage>
)
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
objects: []
};
this.addObject = this.addObject.bind(this)
}
addObject(object) {
this.setState({
objects: [...this.state.objects, object]
});
}
render() {
const { objects } = this.state
return (
<div className="App">
<PlanBuilder objects={objects} />
<PlanMenu addObject={this.addObject} />
</div>
);
}
}
The other alternative is to create a "Container" to hold the logic instead adding it to App, like:
class PlanMenu extends React.Component {
render() {
const { addObject } = this.props
return (
<div className="PlanMenu">
<button type="button"
onClick={addObject(
new CWall({
x: 100,
y: 100,
length: 200
}))}>Wall
</button>
</div>
);
}
}
class PlanBuilder extends React.Component {
render() {
const { objects } = this.props
return (
<Stage>
<Layer>
{objects.map(function(object) {
return object.render();
})}
</Layer>
</Stage>
)
}
}
class PlanContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
objects: []
};
this.addObject = this.addObject.bind(this)
}
addObject(object) {
this.setState({
objects: [...this.state.objects, object]
});
}
render() {
const { objects } = this.state
return (
<div>
<PlanBuilder objects={objects} />
<PlanMenu addObject={this.addObject} />
</div>
)
}
}
class App extends React.Component {
render() {
return (
<div className="App">
<PlanContainer />
</div>
);
}
}
In my opinion, creating a Container makes your code more readable, reusable and cleaner :)
Hope it help!

How can I make a list of integers click on element with corresponding id?

I have a list of ids (integer) and I have multiple components.
After a request to my API, the component receives a list of ids that should already be active.
I want to simulate a click on each element with the same id as the one in my array. I know I can use refs to do that, but I don't undertstand how to make it works with a list of elements.
Here's my code :
import React, { Component } from 'react'
import InterestBox from './InterestBox'
import Axios from 'axios'
export class InterestList extends Component {
constructor(props) {
super(props);
this.state = {pinterests: []}
}
componentDidMount() {
Axios.get('http://localhost:8000/api/interests')
.then((success) => {
this.setState({pinterests: success.data.data.interests});
})
}
componentDidUpdate(prevProps) {
console.log(JSON.stringify(prevProps));
console.log(JSON.stringify(this.props))
if(this.props.alreadyChecked != prevProps.alreadyChecked) {
this.props.alreadyChecked.forEach((item) => {
console.log(item)
})
}
}
render() {
return (
<React.Fragment>
{Object.keys(this.state.pinterests).map((interest) => {
var pinterest = this.state.pinterests[interest];
return <InterestBox id={pinterest.id} onClick={this.props.onClick} icon={pinterest.picture_src} title={pinterest.name} />
})}
</React.Fragment>
)
}
}
export default InterestList
import React, { Component } from 'react'
export class InterestBox extends Component {
constructor(props) {
super(props);
this.images = require('../../img/interests/*.svg');
this.state = {activated: false};
this.interest_box_content = React.createRef();
this.interest_text = React.createRef();
this.handleClick = this.handleClick.bind(this);
this.updateDimensions = this.updateDimensions.bind(this);
}
handleClick() {
this.props.handleClick(this.props.id, this.props.title);
this.setState(prevState => ({
activated: !prevState.activated
}))
}
updateDimensions() {
console.log((window.getComputedStyle(this.refs.interest_box_content).width))
this.refs.interest_text = (window.getComputedStyle(this.refs.interest_box_content).width)
}
render() {
return (
<div className="column is-one-fifth-desktop is-half-touch">
<div className="interest-box">
<div className="interest-box-adjuster">
<div ref={"interest_box_content"} className={"interest-box-content " + (this.state.activated == true ? 'interest-box-activated' : '')} onClick={this.handleClick}>
<img className="interest-icon" src={this.images[this.props.icon]} style={{'height': '50%'}}></img>
<i className="activated-icon fas fa-check"></i>
<span ref={"interest_text"} className="interest-text">{this.props.title}</span>
</div>
</div>
</div>
</div>
)
}
}
export default InterestBox
In the InterestList "componentDidUpdate" method, the value of the item is an integer.
I want to use this integer to "click" on the InterestBox with the corresponding "id".
How can I achieve this ?
You can store an array of elements in one ref, like this:
constructor(props) {
super(props);
this.state = {pinterests: []}
this.pinterestRefs = React.createRef()
}
...
render() {
return (
<React.Fragment>
{Object.keys(this.state.pinterests).map((interest) => {
var pinterest = this.state.pinterests[interest];
return <InterestBox id={pinterest.id} onClick={this.props.onClick} icon={pinterest.picture_src} title={pinterest.name} ref={pinterestRef => this.refs.pinterestRefs.push(pinterestRef)} />
})}
</React.Fragment>
)
}
and then call the click function on each in a componentDidMount function:
componentDidMount() {
if (this.refs.pinterestRefs.length) {
this.refs.pinterestRefs.forEach(pinterestEl => {
pinterestEl.click();
});
}
}
Since this.pinterestRefs is a ref and not an array, the push method is not available. Unfortunately, we do not have a definite length so we can't declare the refs preemptively. However, we can add it to this.refs object and the convert it to an array:
export class InterestList extends Component {
constructor(props) {
super(props);
this.state = {pinterests: []}
}
componentDidMount() {
Axios.get('http://localhost:8000/api/interests')
.then((success) => {
this.setState({pinterests: success.data.data.interests});
})
}
componentDidUpdate(prevProps) {
console.log(Object.values(this.refs)); // Array with all refs
console.log(JSON.stringify(prevProps));
console.log(JSON.stringify(this.props))
if(this.props.alreadyChecked != prevProps.alreadyChecked) {
this.props.alreadyChecked.forEach((item) => {
console.log(item)
})
}
}
render() {
return (
{/*I'm assuming each item has a unique id, if not, create one*/}
<React.Fragment>
{Object.keys(this.state.pinterests).map((interest) => {
var pinterest = this.state.pinterests[interest];
return <InterestBox id={pinterest.id} onClick={this.props.onClick} ref={pinterest.id} icon={pinterest.picture_src} title={pinterest.name} />
})}
</React.Fragment>
)
}
}
export default InterestList;

React pass ajax data to children

How I can pass to child component from parent data requested by ajax call?
For example I have code like that
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
async componentDidMount() {
const response = await fetch();
this.setState(response.data);
}
render() {
return (
<ChildComponent data={this.state} /> // data={}
);
}
}
Problem here is ChildComponent will be mount before data will be fetch so I will get empty object data in ChildComponent.
Check if data is available or not
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
async componentDidMount() {
const response = await fetch();
this.setState(response.data);
}
render() { // Return if response is Object
return Object.keys(this.state).length > 0
? <ChildComponent data={this.state} /> // data={}
: <div>Loading...</div>
}
render() { // Return if response is Array
return this.state.length > 0
? <ChildComponent data={this.state} /> // data={}
: <div>Loading...</div>
}
}
You can decide to render the children only if there is some data.
For this, maybe don't replace the whole state with data but create a separate key. It will be easier if you need to add some other state after.
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
async componentDidMount() {
const response = await fetch();
this.setState({ data: response.data });
}
render() {
const { data } = this.state;
return data ? <ChildComponent data={data} /> : null;
}
}

React Native - How to append to parent state array while in child component (StackNavigators)?

My project is looping through a data array in a child component Main, and I'm trying to update the state in parent component, App, on an event (swiping right on a card in Main), so that I could access the data that was 'swiped right' on a sibling Component in Favorites. Hopefully that makes sense?
The project structure is as such:
App
|__ Rootstack
|
|__Favorites
|__Main
In my Main component, I am mapping the collection array and looping thru:
collection = imagedata;
// a local JSON array of data that I am looping thru in Main
class Main extends React.Component {
_toFavs = () => {
this.props.navigation.navigate('Favorites');
};
render() {
const contents = collection.map((item, index) => {
return (
<Card key={index}>
......
</Card>
)
});
return (
<View>
<CardStack
onSwiped={() => {console.log('onSwiped')}
onSwipedRight={() => console.log('onSwipedLeft')}>
//
//HERE IS THE PART - HOW TO UPDATE THE 'favoritesList' array in the parent 'App's state?
//
{contents}
</CardStack>
</View>
);
}
}
const RootStack = StackNavigator(
{
Main: {
screen: Main},
Favorites: {
screen: Favorites}
},
{
initialRouteName: 'Main'
}
);
class Favorites extends React.Component {
// The plan is to eventually access the favoritesList array in App's state here and display cards that were swiped right in the Main component.
_onPress = () => {
this.props.navigation.navigate('Main');
};
render() {
return (
<View><Text>Hello!</Text></View>
);
}
}
export default class App extends Component<{}> {
constructor(props) {
super(props);
this.state = {
favoritesList: []
};
}
render() {
return <RootStack />;
}
}
I've come across some other answers of updating state such as
this.setState({ favoritesList: [...this.state.favoritesList, 'new value'] }), but how can I do this to the .state of App while i'm inside a child component Main?
Thanks in advance!
collection = imagedata;
// a local JSON array of data that I am looping thru in Main
class Main extends React.Component {
_toFavs = () => {
this.props.navigation.navigate('Favorites');
};
render() {
const contents = collection.map((item, index) => {
return (
<Card key={index}>
......
</Card>
)
});
return (
<View>
<CardStack
onSwiped={() => {console.log('onSwiped')}
onSwipedRight={() => {console.log('onSwipedLeft') ;
this.props.screenProps()}}>
//
{contents}
</CardStack>
</View>
);
}
}
const RootStack = StackNavigator(
{
Main: {
screen: Main},
Favorites: {
screen: Favorites}
},
{
initialRouteName: 'Main'
}
);
class Favorites extends React.Component {
// The plan is to eventually access the favoritesList array in App's state here and display cards that were swiped right in the Main component.
_onPress = () => {
this.props.navigation.navigate('Main');
};
render() {
return (
<View><Text>Hello!</Text></View>
);
}
}
export default class App extends Component<{}> {
constructor(props) {
super(props);
this.state = {
favoritesList: []
};
}
updateArr=()=>{consol.log("fire") }
render() {
return <RootStack screenProps={this.updateArr} />;
}
}
i hope it solve your problem
update props-name

Categories

Resources