How to re-render parent component on child component's button click - javascript

I am learning react and I would still consider myself to be a beginner. My goal is to click a button on my child component so that I could re render the parent component. This is the code I have.
Parent Component
import React, { Component } from 'react';
import Activity from './Components/Activity'
class App extends Component {
state = {
activity: ''
}
handleClick = () => {
// I have read that forceUpdate is discouraged but this is just an example
this.forceUpdate()
}
async componentDidMount() {
const url = 'http://www.boredapi.com/api/activity/'
const response = await fetch(url);
const data = await response.json();
this.setState({
activity: data.activity
})
}
render() {
return (
<div>
<Activity act={this.state.activity} click={this.handleClick}/>
</div>
);
}
}
export default App;
Child Component
import React, { Component } from 'react';
class Activity extends Component {
render() {
return (
<div>
<h1>Bored? Here is something to do</h1>
<p>{this.props.act}</p>
<button onClick={this.props.click}>Something Else</button>
</div>
);
}
}
export default Activity;
As you can see I am trying to click a button so that I could get another fetch and a different activity renders on my child component. I am trying to keep my child component stateless but if keeping it stateless doesn't make sense or is just plain wrong I would love to know.

You can try to move fetching function outside componentDidMount
for the example:
handleClick = () => {
this.fetchdata();
}
async fetchdata(){
const url = 'http://www.boredapi.com/api/activity/'
const response = await fetch(url);
const data = await response.json();
this.setState({
activity: data.activity
})
}
componentDidMount() {
this.fetchdata();
}

You can make a class method for fetching the new activity,
Call it after the app first mounted with componentDidMount() and again when you call it from the child component Activity.
You should mentioned in the your question that the response body is different in each request you make.
import React, { Component } from 'react';
import Activity from './Activity'
class App extends Component {
state = {
activity: ''
}
handleClick = () => {
this.getActivity()
}
componentDidMount() {
this.getActivity();
}
async getActivity() {
const url = 'https://www.boredapi.com/api/activity/'
const response = await fetch(url);
const data = await response.json();
this.setState({
activity: data.activity
})
}
render() {
console.log(this.state);
return (
<div>
<Activity act={this.state.activity} click={this.handleClick}/>
</div>
);
}
}
export default App;
Here is also a sandbox:
https://codesandbox.io/s/dreamy-noether-q98rf?fontsize=14&hidenavigation=1&theme=dark

Related

How do I make a client-side only component for GatsbyJS?

How do I create a component for Gatsby that will load on the client-side, not at build time?
I created this one and it renders with gatsby develop but not with the rendered server-side rendering
import React from 'react';
import axios from 'axios';
import adapter from 'axios-jsonp';
export default class Reputation extends React.Component<{}, { reputation?: number }> {
constructor(props) {
super(props);
this.state = {};
}
async componentDidMount() {
const response = await axios({
url: 'https://api.stackexchange.com/2.2/users/23528?&site=stackoverflow',
adapter
});
if (response.status === 200) {
const userDetails = response.data.items[0];
const reputation = userDetails.reputation;
this.setState({
reputation
});
}
}
render() {
return <span>{ this.state.reputation?.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") }</span>
}
}
If you don't want the component to be bundled in the main js file at build time, use loadable-components
Install loadable-components and use it as a wrapper for a component that wants to use a client-side only package. docs
import React, { Component } from "react";
import Loadable from "#loadable/component";
const LoadableReputation = Loadable(() =>
import("../components/Reputation")
);
const Parent = () => {
return (
<div>
<LoadableReputation />
</div>
);
};
export default Parent;
before render this component, make sure you have a window, to detect if there is a window object. I would write a hook for that:
function hasWindow() {
const [isWindow, setIsWindow] = React.useState(false);
React.useEffect(() => {
setIsWindow(true);
return ()=> setIsWindow(false);
}, []);
return isWindow;
}
In the parent component check if there is a window object:
function Parent(){
const isWindow = hasWindow();
if(isWindow){
return <Reputation />;
}
return null;
}

Issue Passing Props After Fetch Request To Child Component (React)

I’m having trouble passing the state data from a parent component to a child component. I’m not sure why this is happening, so any feedback is greatly appreciated.
My fetch request is returning the correct data when I console.log ‘this.state.episodeData’ in the componentDidMount() method in the parent component, but it is not showing in the console.log in the componentDidMount() method in the child component. What am I doing wrong?
I’ve simplified the example to show only the relevant fetch request and data handling:
Parent component
import React, { Component, useState, Fragment } from 'react';
import TempComp from './tempComp';
export default class PostContent extends Component {
constructor(props) {
super(props);
this.state = {
id: '',
episodeData: [],
}
}
async componentDidMount() {
const { id } = this.props.match.params;
const response = await fetch(`http://localhost:5000/episode/${id}/playlist`);
const jsonData = await response.json();
this.setState({
episodeData: jsonData, //this is working!
id: id
});
console.log('parent fetched data', this.state.episodeData)
}
render() {
return (
<Fragment>
<TempComp playlist={this.state.episodeData} />
</Fragment>
)
}
}
Child component
import React, { Component } from 'react'
class TempComp extends Component {
constructor(props) {
super(props);
this.state = {
}
}
componentDidMount() {
console.log(‘props in child component’, this.props.playlist)
}
render() {
return (
<div>
</div>
)
}
}
export default TempComp
componentDidMount is called just the first time after the initial rendering of your component. This means that your child component is rendered before you finish your fetch request. In the parent component you are not seeing it, because setState works asynchronously and it is not done saving your state, when you try to print it out. If you want to see it, pass callback to setState:
async componentDidMount() {
const { id } = this.props.match.params;
const response = await fetch(`http://localhost:5000/episode/${id}/playlist`);
const jsonData = await response.json();
this.setState({
episodeData: jsonData, //this is working!
id: id
}, () => {
console.log('parent fetched data', this.state.episodeData)
);
}
In order to see the updated data in your child component, consider using componentDidUpdate(prevProps, prevState, snapshot):
componentDidUpdate(prevProps, prevState, snapshot) {
console.log('props in child component', this.props.playlist) ;
}

Call a method from another Component after the Async function reactjs

I have 2 components, the first component has a function that calls after the async function of the second component, what I want to do is something like vue's this.$emit() function that calls a listener from that component anytime, how can I do that in react?
This is my first component
import React, { Component } from 'react';
import SecondComponent from '../Path/to/second/component'
class MainMenu extends Component {
callThis (data) {
console.log(data)
}
render () {
return <SecondComponent onDataReceived = {this.callThis} />
}
}
export default FirstComponent
And this is my SecondComponent
import React, { Component } from 'react';
class SecondComponent extends Component {
async asyncFunction () {
const data = await getDataFromApi()
// call the function from first component here...
}
render () {
return <button onClick={() => this.asyncFuncion} />
}
}
export default FirstComponent
Your second component must invoke asyncFuncion, and then inside asyncFuncion you can call the callThis function from the props
class SecondComponent extends Component {
async asyncFunction () {
const data = await getDataFromApi()
this.props.onDataReceived(data)
}
render () {
return <button onClick={() => this.asyncFuncion()} />
}
}
and do not forget to bind that callThis as well, or just use fat arrow function:
class MainMenu extends Component {
callThis = (data) => {
console.log(data)
}
On your first component, you are sending a props to your second components.
Here is the documentation : https://reactjs.org/docs/components-and-props.html
To access onDataReceived in your second component you could write :
async asyncFunction () {
const data = await getDataFromApi()
this.props.onDataReceived(data);
}
this is how you can receive data/use methods from parent passed props:
async asyncFunction () {
const data = await getDataFromApi()
// call the function from first component here...
this.props.onDataReceived(data);
}

React - Passing fetched data from API as props to components

Iam trying to understand and learn how to pass around data as props to other components to use. Iam trying to build a top-level hierarchy where the API Request is made in a class at top level and then the result is passed around to child components to be used as props and then in states.
The problem is that when i pass the result i get "Object Promise" in my child component. How do I access the data sent as props to child components?
As you can see in my App.js in my render() method that i created a component of the class API and pass the result from the fetchData() method as parameter to the component.
In my API.js class i used console.log to check the result but
the result i get from the logs are:
line 5: {dataObject: Promise}
line 10: undefined
App.js:
import API from './API';
class App extends Component {
componentDidMount(){
this.fetchData();
}
fetchData(){
const url = "https://randomuser.me/api/?results=50&nat=us,dk,fr,gb";
return fetch(url)
.then(response => response.json())
.then(parsedJSON => console.log(parsedJSON.results))
.catch(error => console.log(error));
}
render() {
return (
<div className="App">
<API dataObject={this.fetchData()}/>
</div>
);
}
}
export default App;
API.js
import React from 'react';
class API extends React.Component{
constructor(props){
console.log(props);
super(props);
this.state = {
dataObj:props.dataObject
};
console.log(this.state.dataObject)
}
render() {
return(
<p>""</p>
)
}
}
export default API;
Try changing App.js to this:
import API from './API';
class App extends Component {
componentDidMount(){
this.fetchData();
}
fetchData(){
const url = "https://randomuser.me/api/?results=50&nat=us,dk,fr,gb";
return fetch(url)
.then(response => response.json())
.then(parsedJSON => this.setState({results: parsedJSON.results}))
.catch(error => console.log(error));
}
render() {
return (
<div className="App">
<API dataObject={this.state.results}/>
</div>
);
}
}
export default App;
This makes sure you fetch the data in componentDidMount and it now uses state to store the data which then will be passed into your API component.
If anyone is looking for an answer using Hooks then this might help.
App.js
import API from './API';
function App(props) {
const [result, setResult] = React.useState({});
// similar to componentDidMount
React.useEffect(() => {
this.fetchData();
}, []);
fetchData() {
const url = "https://randomuser.me/api/?results=50&nat=us,dk,fr,gb";
fetch(url)
.then(response => setResult(response.json()))
.catch(error => console.log(error));
}
return (
<div className="App">
<API dataObject={result}/>
</div>
);
}
export default App;
API.js
import React from "react";
function API(props) {
const [result, setResult] = React.useState(props.dataObject);
React.useEffect(() => {
setResult(result);
}, [result]);
return <p>{result}</p>;
}
export default API;
Hope it helps! And let me know if anything is incorrect.
You should fetch data in componentDidMount and not in render. Fetching the data within render causes the API request to be repeated, every time the DOM is re-rendered by react.js.
After making the GET request to the API endpoint, first parse the data into a javascript object, then set the results to state using this.setState from within your component.
From there, you may pass the data held in state to child components as props in the render function.
For example:
const App = (props) =>
<ChildComponent />
class ChildComponent extends React.Component {
constructor(props){
super(props);
this.state = {
results: []
}
}
componentDidMount(){
fetch('/api/endpoint')
.then(res => res.json())
.then(results => this.setState({results})
}
render(){
return <GrandchildComponent {...this.state} />
}
}
const GrandchildComponent = (props) =>
<div>{props.results}</div>

ReactJs - how can I pass data inside one Component

I want to pass data from axiosDidMount function to
<p className='title' id='boldTitle'>{data goes here}</p>
I can console.log data and it is working and in my example it is a string "New York City".
I got to the point when I write some input in Search.js Component and it is passed to Results.js Component by this.props.userQuery. So the response.data[1][1] is updating correctly and live in console.log as I write input but I have problem with passing this data that I'm getting from Wikipedia to final destination.
What is proper way to pass this data in this example?
import React from 'react';
import axios from 'axios';
export default class Results extends React.Component {
axiosDidMount(userQuery) {
//const fruits = [];
const wikiApiUrl = 'https://en.wikipedia.org/w/api.php?action=opensearch&format=json&origin=*&search=';
const wikiApiUrlWithQuery = wikiApiUrl + userQuery;
axios.get(wikiApiUrlWithQuery)
.then(response => {
console.log(response.data[1][1]); //New York City
console.log(typeof(response.data[1][1])); //string
//console.log(response.data[2])
//console.log(response.data[3])
//fruits.push(response.data[1]);
})
.catch(err => {
console.log('Error: =>' + err);
});
//return fruits;
}
render() {
//this.props.userQuery from Search.js
const test = this.axiosDidMount(this.props.userQuery);
return(
<div>
<a className='title' href="" target='_blank'>
<div className='result'>
<p className='boldTitle'>{data goes here}</p>
<p></p>
</div>
</a>
</div>
);
}
}
You should separate your concerns. Make a data receiving component, or a container component that handles data retrieval and conditionally renders the component requiring the data once it's available. Something along the lines of the following:
import React, { Component } from 'react';
import axios from 'axios';
const PresentationComponent = (props) => {
// return mark with data
}
const PlaceHolderComponent = (props) => {
// return placeholder markup
}
export default class DataReceivingWrapper extends Component {
constructor(props) {
super(props);
this.state = {
data: null
}
}
componentDidMount() {
axios.get(...)
.then(data) {
this.setState(Object.assign({}, this.state, { data: data }))
}...
}
render() {
if (this.props.data) {
return <PresentationComponent />;
} else {
return <PlaceHolderComponent />; // or null
}
}
}

Categories

Resources