React - Call setState in parent when child is called - javascript

I am building a blog in react where I get the data from a JSON-API and render it dynamically. I call setState in my app.js to get the data from JSON via axios and route with match.params to the post pages(childs). This works fine, BUT if I call the child (URL: ../blog/post1) in a seperate window it doesnt get rendered. It works if I hardcode the state.
So, I see the issue, what would be the best way to fix it?
Parent:
class App extends Component {
constructor(props) {
super();
this.state = {
posts: []
}
getPosts() {
axios.get('APIURL')
.then(res => {
let data = res.data.posts;
this.setState({
posts: data
})
})
}
componentDidMount = () => this.getPosts()
Child:
UPDATE - Found the error:
The component threw an error, because the props were empty. This was because the axios getData took some time.
Solution: I added a ternary operator to check if axios is done and displayed a css-spinner while loading. So no error in component.
Dynamic Routing works like a charme.

You can do this with binding. You would need to write a function like
setPosts(posts) {
this.setState({posts});
}
in the parent, and then in the parent, bind it in the constructor, like so.
this.setPosts = this.setPosts.bind(this);
When you do this, you're attaching setPosts to the scope of the parent, so that all mentions of this in the function refer to the parent's this instead of the child's this. Then all you need to do is pass the function down to the child in the render
<Child setPosts={this.setPosts} />
and access that method in the child with
this.props.setPosts( /*post array goes here */ );
This can apply to your getPosts method as well, binding it to the parent class and passing it down to the Child.

When you hit the /blog/post1 url, it renders only that page. The App component is not loaded. If you navigate from the App page, all the loading has been done and you have the post data in the state of the child component.
Please refer to this SO question. This should answer your question.

Related

React componentDidMount confusion

I'm confused by some behavior and wondering if someone can help. I have a React component which fetches movie data depending on the filter passed in through props. Using console.log I can see that my componentDidMount() is only being called once, yet each time the component is re-rendered due to receiving different props a state variable that is only set in componentDidMount() changes. My code is quite long so I don't want to post it all but I can if needed. The snippet that is causing my confusion is below:
class MoviesList extends React.Component {
constructor(props) {
super(props);
this.state = {
filterList : [], filter: {}
};
}
async componentDidMount() {
console.log("shouldOnlyCallOnce");
if( this.props.filter.on) {
if (this.props.filter.type == "title"){
try {
const url = "mycustomapi.com/api/"+this.props.filter.title;
const response = await fetch(url);
const jsonData = await response.json();
jsonData.sort((a, b) => a.title.localeCompare(b.title));
console.log("??");
this.setState({filterList: jsonData, filter: this.props.filter});
}
catch (error) {
console.error(error);
}
}
Despite the state.filter only being set there in the whole component, it changes each time my component is reloaded. Does anyone know how this could happen without console logging multiple times?
As I see it, the only explanation is that you are modifying the contents of the filter outside this component.
Consider this:
let filterA = { title: 'foo' }
let filterB = filterA
console.log(filterB.title) // Shows 'foo'
filterA.title = 'bar'
console.log(filterB.title) // Shows 'bar'
Even though we never directly modified filterB here, the value changed. This is because filterA and filterB are referencing the same object, so of course they both change.
If you in your case pass the filter as a prop, and later modify the filter inside the parent, the filter will have changed in the child as well.
As a side note: What you are doing is called deriving state from props, which means that you are using the props to set the state. This is almost never good practice. https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html
ComponentDidMount will trigger when a component will do intiate render. After a props or state change, ComponentDidUpdate will trigger .Please check the link : react life cycle example

Is there a beter way to handle response from async call instead of doing setState

I am using javascript async await to make a service call to the server, mostly from componentDidMount or componentDidUpdate, but when I want to send the response as props to other components, I am currently updating the state of the component with the response. But since as the user I dont actually modify that particular state, it kinda feels like a wasted variable. Is there any way to get it as props to my component instead of setting the state.
class Example extends React.Component {
constructor(props){
super(props);
this.state = {data: {}}
}
componentDidMount(){
const {data} = await getService();
this.setState({data})
}
render(){
return (<ChildComponent data/>)
}
}
Is it possible to get the data which is a property from response as props instead of setting it as state and retrieving it from state
Assuming your example is a scaled down version of your actual app, I would say what you are doing is fine. Otherwise, I would have a minor query around why not have ChildComponent do they fetching for data to avoid 'prop drilling' i.e. passing props through multiple layers of components to eventually reach a component that cares about the data.
It's not fine to make an async call in componentDidMountbecause whenever the state is changed, the componentDidMount is called ==> there is a lot of call to the service.
there is a more elegant way to do it, using Redux(state management tool which contains the state of the application), by doing #async(make here all your call services and automatically are connected to other component without passing it in props)

Access state of a component in a test

I have a component that change some of his props when we inject different props.
I'm struggling to find a simple way to acces the state of my shallowed component from my test
Here is the code :
describe('componentWillReceiveProps', () => {
it('update state isDedicatedDealPriceSelected to true', () => {
const productComponent = shallow(<Product selectedPriceOption="Test" />);
productComponent.setProps({ selectedPriceOption: 'dedicatedDealPrice' });
expect(productComponent.props.isDedicatedDealPriceSelected).toBeTruthy();
});
});
I got undefined, i want to access the props isDedicatedDealPriceSelected that should be truthy. I think i'm miswritting something here on the last line in productComponent.props.isDedicatedDealPriceSelected
How can i access the props of my component ?
I'm using enzime to shallow render my component in the test with jest.
Thankd in advance !
EDIT : i was not looking to access the props, but instead the state ! sorry for the mispelling
It seems setProps takes a callback executed after the re-render. Maybe your test needs to be async and the assertion to be done inside this callback.
From the examples, only componentWillReceiveProps seems to be called synchronously.
https://airbnb.io/enzyme/docs/api/ShallowWrapper/setProps.html
To access the state of a shallow rendered component, you can access it using :
const wrapper = shallow(<component />);
wrapper.state().componentVar
To access a function of a shallow rendered component, you can access it using :
wrapper.instance().componentFunction()

Props not being passed - React Native

I am using react-native-modalbox to show a modal and save data as a result.
When I save the data, my modal has access to the parent flat list so I can call a getData() function correctly, and the flat list reflects the latest update:
Modal - saveItem() is executed when a save button is pressed.
saveItem = async () => {
...
this.props.parentFlatList.getData();
this.props.progress.getData(); //This function call returns an error
this.closeModal();
};
Parent - the onPressAdd function opens the modalbox
constructor(props) {
super(props);
this.state = {
...
};
this.onPressAdd = this.onPressAdd.bind(this);
}
...
onPressAdd = () => {
this.refs.addFoodModal.showAddFoodModal();
}
<AddFoodModal ref={'addFoodModal'} parentFlatList={this} />
However, when I try to link the modal to another parent I receive this error:
Possible Unhandled Promise Rejection (id: 0):
TypeError: Cannot read property 'getData' of undefined
I simply want the modal to call the getData() functions in both parents so they both receive the updated data in their states.
Another Parent - this does not open the modal but I assumed it could pass itself down as props as the other parent does?
<AddFoodModal ref={'addFoodModal'} progress={this} />
Not sure where you get progress, if I understand correctly, you can pass progress to ur parent component as a props like <Parent progress={progress} />, then in ur child modal component you can do .
Also passing this down to child component is not a good practice, you should only pass what you actually need to ur child component to avoid unnecessary re-rendering, which will affect performance. In your code, you should pass getData down to the child component as a props, not this.

React, Apollo 2, GraphQL, Authentication. How to re-render component after login

I have this code: https://codesandbox.io/s/507w9qxrrl
I don't understand:
1) How to re-render() Menu component after:
this.props.client.query({
query: CURRENT_USER_QUERY,
fetchPolicy: "network-only"
});
If I login() I expect my Menu component to re-render() itself. But nothing.
Only if I click on the Home link it re-render() itself. I suspect because I'm using this to render it:
<Route component={Menu} />
for embrace it in react-router props. Is it wrong?
Plus, if inspect this.props of Menu after login() I see loading: true forever. Why?
2) How to prevent Menu component to query if not authenticated (eg: there isn't a token in localStorage); I'm using in Menu component this code:
export default graphql(CURRENT_USER_QUERY)(Menu);
3) Is this the right way to go?
First, let me answer your second question: You can skip an operation using the skip query option.
export default graphql(CURRENT_USER_QUERY, {
skip: () => !localStorage.get("auth_token"),
})(Menu);
The problem now is how to re-render this component when the local storage changes. Usually react does not listen on the local storage to trigger a re-render, instead a re-render is done using one of this three methods:
The component's state changes
The parent of the component re-renders (usually with new props for the child)
forceUpdate is called on the component
(Note that also a change of a subscribed context will trigger a re-render but we don't want to mess around with context ;-))
You might want to go with a combination of 2. and 3. or 1. and 2.
In your case it can be enough to change the route (as you do in the login function). If this does not work you can call this.forceUpdate() on the App component after Login using a callback property on <Login onLogin={() => this.forceUpdate() } />.
EDITED
1) I just created new link https://codesandbox.io/s/0y3jl8znw
You want to fetch the user data in the componentWillReceiveProps method:
componentWillReceiveProps() {
this.props.client.query({query: CURRENT_USER_QUERY})
.then((response) => {
this.setState({ currentUser: response.data.currentUser})
})
.catch((e) => {
console.log('there was an error ', e)
})
}
This will make the component re-render.
2) Now when we moved the query call in the component's lifecycle method we have full control over it. If you want to call the query only if you have something in localstorage you just need to wrap the query in a simple condition:
componentWillReceiveProps() {
if(localstora.getItem('auth_token')) {
this.props.client.query({query: CURRENT_USER_QUERY})
.then((response) => {
this.setState({ currentUser: response.data.currentUser})
})
.catch((e) => {
console.log('there was an error ', e)
})
}
}
3) You want to store the global application state in redux store. Otherwise you will have to fetch the user info every time you need to work with it. I would recommend to define a state in you App component and store all the global values there.

Categories

Resources