I'm creating a block for Wordpress with Gutenberg Editor, which is working on React js.
So I'm calling Wordpress API by apiFetch(), which is same to fetch():
class PortfolioTagsEdit extends Component {
constructor(props) {
super(props);
this.state = {
data: [],
isLoading: false,
};
}
componentDidMount() {
const { attributes } = this.props;
const { switcher } = attributes;
this.setState({ isLoading: true });
apiFetch( { path: `/wp/v2/${switcher}?post` } )
.then(data => this.setState({ data, isLoading: false }));
}
...
}
For variable switcher I have controllers which are changing the value.
My problem is when I switch the value of switcher I should reload api call, but I don't know how)
Can you help me, please?
Using react hooks you can use useEffect for fetching API.
function PortfolioTagsEdit({ attributes }) {
// state
const [loading, setLoading] = useState(false);
const [data, setData] = useState([])
// here useEffect will run on component mount and every-time attributes.switcher changes
useEffect(() => {
setLoading(true)
apiFetch( { path: `/wp/v2/${switcher}?post` } )
.then(data => {
setLoading(false)
setData(data)
});
}, [attributes.switcher])
return (
....
)
}
The easiest way to do this would be to have the switcher variable in state. You can then implement the componentDidUpdate method to call your apiFetch:
class PortfolioTagsEdit extends Component {
constructor(props) {
super(props);
this.state = {
data: [],
isLoading: false,
switcher: this.props.attributes.switcher
};
}
componentDidMount() {
this.callAPI()
}
componentDidUpdate(prevProps, prevState) {
if (prevState.switcher !== this.state.switcher) this.callAPI();
}
callAPI() {
const { switcher } = this.state;
this.setState({ isLoading: true });
apiFetch( { path: `/wp/v2/${switcher}?post` } )
.then(data => this.setState({ data, isLoading: false }));
}
...
}
Check out the docs for componentDidUpdate - https://reactjs.org/docs/react-component.html#componentdidupdate
You could also take a look on how to do this using hooks, specifically useEffect -https://reactjs.org/docs/hooks-reference.html#useeffect
Related
For the web app I'm building in React, I need to record audio and be able to somehow put that recorded audio in the app's global state so I can use and manipulate that recorded audio in different components of the app.
My global state is setup using React Hooks (made and managed with useReducer, createContext, useContext) and I believe Hooks only work for functional components, not class components.
So the issue I'm running up against is that every tutorial I've followed to get my browser microphone to work uses class components (like the code below), not functional components. And I'm assuming that this is for good reason because when I've tried to translate these class components into functional components, I get the error: "cannot read property 'finish' of 'undefined'"
Are there ways to take this audio data (blobURL) and pass it to my global state?
Alternatively (and ideally), is there a way to use the microphone to record audio in a functional component instead of a class component?
import MicRecorder from "mic-recorder-to-mp3";
import React from "react";
const Mp3Recorder = new MicRecorder({ bitRate: 128 });
class AudioRecorder extends React.Component {
constructor(props) {
super(props);
window.AudioContext = window.AudioContext || window.webkitAudioContext;
this.state = {
isRecording: false,
isPaused: false,
blobURL: "",
isBlocked: false
};
}
startRecording = () => {
if (this.state.isBlocked) {
console.log("Please give permission for the microphone to record audio.");
} else {
Mp3Recorder.start()
.then(() => {
this.setState({ isRecording: true });
})
.catch(e => console.error(e));
}
};
stopRecording = () => {
this.setState({ isRecording: false });
Mp3Recorder.stop()
.getMp3()
.then(async ([buffer, blob]) => {
const blobURL = URL.createObjectURL(blob)
this.setState({
blobURL: blobURL,
isRecording: false
});
})
.catch(e => console.log(e));
};
checkPermissionForAudio = () => {
if (navigator.mediaDevices === undefined) {
navigator.mediaDevices = {};
}
if (navigator.mediaDevices.getUserMedia === undefined) {
navigator.mediaDevices.getUserMedia = function(constraints) {
// First get ahold of the legacy getUserMedia, if present
var getUserMedia =
// navigator.getUserMedia ||
navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
// Some browsers just don't implement it - return a rejected promise with an error
// to keep a consistent interface
if (!getUserMedia) {
return Promise.reject(
new Error("getUserMedia is not implemented in this browser")
);
}
// Otherwise, wrap the call to the old navigator.getUserMedia with a Promise
return new Promise(function(resolve, reject) {
getUserMedia.call(navigator, constraints, resolve, reject);
});
};
}
navigator.mediaDevices
.getUserMedia({ audio: true })
.then(stream => {
this.setState({ isBlocked: false });
})
.catch(err => {
this.setState({ isBlocked: true });
console.log("Please give permission for the microphone to record audio.");
console.log(err.name + ": " + err.message);
});
};
componentDidMount() {
this.checkPermissionForAudio();
}
render() {
const { isRecording } = this.state;
return (
<React.Fragment>
<button
onClick={this.startRecording}
className="mr-3 add-collec-btn"
disabled={isRecording}
>
Record
</button>
<button
onClick={this.stopRecording}
className="mr-3 delete-btn"
disabled={!isRecording}
>
Stop
</button>
<audio
ref="audioSource"
controls="controls"
src={this.state.blobURL || ""}
/>
</React.Fragment>
);
}
}
export default AudioRecorder;
UPDATE:
This is how I've set up Context in my application and how it's provided through the code. In my store folder, I have three files: Context.js, GlobalStateProvider, and useGlobalState.
Context.js
import { createContext } from 'react';
const Context = createContext({});
export default Context;
GlobalStateProvider.js
This wraps everything in my App.js file
import React from 'react';
import useGlobalState from './useGlobalState';
import Context from './Context';
const GlobalStateProvider = ({ children }) => {
return (
<Context.Provider value={useGlobalState()}>{children}</Context.Provider>
);
}
export default GlobalStateProvider;
useGlobalState.js
import { useReducer } from 'react';
const reducer = (state, action) => {
switch (action.type) {
case 'SETISRECORD':
return {
...state,
isRecording: action.payload
}
case 'SETISBLOCKED':
return {
...state,
isBlocked: action.payload
}
case 'setBlobURL':
return {
...state,
blobURL: action.payload
}
default: {
return state;
}
}
};
const useGlobalState = () => {
const [globalState, globalDispatch] = useReducer(reducer, {
isRecording: false,
isBlocked: false,
blobURL: '',
});
return { globalState, globalDispatch };
}
export default useGlobalState;
I then interface with my global state in functional components like so:
const functionalComponent = () => {
const { globalState, globalDispatch } = useContext(Context);
return (
[code]
);
}
Your class-based components can still "consume" the context but the syntax is a little more involved than simply using a useContext React hook.
Context.Consumer
For your case you would import your global state context Context and render the component that needs to access the context via a function child. The child component would then need consume these context values via props.
Some classed-based component:
class MyComponent extends React.Component {
...
render() {
const { myContext: { globalState, globalDispatch } } = this.props;
return (
[code]
);
}
}
Wrap and pass via props:
import MyContext from '../path/to/context';
...
<MyContext.Consumer>
{myContext => <MyComponent myContext={myContext} />}
</MyContext.Consumer>
What I want to do
When a child component first rendering, I would like to use the value in props from a parent component
Problem
When a child component is first rendered, props is not set to state in the child component
I am a beginner to React. I am trying to use props in order to call API by axios in componentDidMount in a child component. I mean, what I am doing is calling API in a parent component and setting data from this API to a child component as props.
However, when I try to do that, props is not set to state in a child component.
For example, when I retrieve product which has some category, I type localhost:3000/api/parent/?category=1/. But, my console.log shows me localhost:3000/api/parent/?category=undefined because I guess props is not set when a child component first rendering.
Actually, I can see category object in state like below.
I guess props is completely set to state after the child component finish first rendering.
How could I set props which comes from API to state?
Although I tried many solutions I found on the stackoverflow, I got stuck at this problem so long time.
I would like you to tell me some solutions.
Thank you very much.
== == ==
My code is like this.
Parent Component
class Top extends Component {
constructor(props) {
super(props);
this.state = {
loginUser: '',
categories: [],
};
}
async componentDidMount() {
const localhostUrl = 'http://localhost:8000/api/';
const topCategoryList = ['Smartphone', 'Tablet', 'Laptop'];
let passCategoryToState=[]
axios
.get(localhostUrl + 'user/' + localStorage.getItem('uid'))
.then((res) => {
this.setState({ loginUser: res.data });
})
.catch((err) => console.log(err));
await Promise.all(
topCategoryList.map(async (category) => {
await axios.get(localhostUrl + 'category/?name=' + category).then((res) => {
passCategoryToState=[...passCategoryToState, res.data]
console.log(passCategoryToState);
});
})
);
this.setState({categories : passCategoryToState})
}
render() {
if (!this.props.isAuthenticated) {
return <p> Developing now </p>;
}
if (this.state.loginUser === '' ) {
return <CircularProgress />;
} else {
return (
<>
<Header loginUser={this.state.loginUser} />
<Give_Item_List
axiosUrl="http://localhost:8000/api/"
subtitle="Smartphone Items"
loginUser={this.state.loginUser}
category={this.state.categories[0]}
/>
</>
);
}
}
}
export default Top;
And, child component
class Give_Item_List extends Component {
constructor(props) {
super(props);
this.state = {
loading: false,
loginUser: this.props.loginUser,
category: this.props.category,
};
}
async componentDidMount() {
let pickedGiveItems;
await this.setState({ loading: true });
await axios
.get(this.props.axiosUrl + 'giveitem/?category=' + this.state.category.id)
.then((res) => {
pickedGiveItems = res.data;
console.log(pickedGiveItems);
})
.catch((err) => console.log('Not found related to Items'));
this.setState({ loading: false });
}
render() {
if (this.state.loading == true) {
return <CircularProgress />;
}
return <h1>Give_Item_List</h1>;
}
}
export default Give_Item_List;
==============
Edit:Change to componentDidUpdate
componentDidUpdate(prevProps) {
if(prevProps.category != this.props.category){
let pickedGiveItems;
this.setState({ loading: true });
axios
.get(this.props.axiosUrl + 'giveitem/?category=' + this.props.category.id)
.then((res) => {
pickedGiveItems = res.data;
console.log(pickedGiveItems);
})
.catch((err) => console.log('NotFount'));
this.setState({ loading: false });
}
}
It still doesn't work...
In the constructor, this.props is undefined, but you can access the props directly.
constructor(props) {
super(props);
this.state = {
loading: false,
loginUser: props.loginUser,
category: props.category,
};
}
However, I should note now that storing props in state is a common react anti-pattern, you should always consume prop values from this.props where you need them.
For example:
async componentDidMount() {
let pickedGiveItems;
await this.setState({ loading: true });
await axios
.get(this.props.axiosUrl + 'giveitem/?category=' + this.props.category.id)
.then((res) => {
pickedGiveItems = res.data;
console.log(pickedGiveItems);
})
.catch((err) => console.log('Not found related to Items'));
this.setState({ loading: false });
}
You also can't await a react state update since it isn't an async function nor does it return a Promise. Just provide an initial true loading state and toggle false when the fetch request resolves.
class Give_Item_List extends Component {
constructor(props) {
super(props);
this.state = {
loading: true
};
}
async componentDidMount() {
let pickedGiveItems;
await axios
.get(this.props.axiosUrl + 'giveitem/?category=' + this.props.category.id)
.then((res) => {
pickedGiveItems = res.data;
console.log(pickedGiveItems);
})
.catch((err) => console.log('Not found related to Items'));
this.setState({ loading: false });
}
render() {
if (this.state.loading) {
return <CircularProgress />;
}
return <h1>Give_Item_List</h1>;
}
}
I'm trying to do a basic API fetch and show that information onClick using a button called GENERATE. All it should do for now is show the first url in the json I receive.
Once that is achieved, I want it to show the next url on each click.
App.js
import React, { Component } from 'react';
import { ThemeProvider, createToolkitTheme } from 'internaltools/theme';
import { AppHeader } from 'internaltools/app-header';
const LIGHT_THEME = createToolkitTheme('light');
const DARK_THEME = createToolkitTheme('dark');
const API = 'https://hn.algolia.com/api/v1/search?query=';
const DEFAULT_QUERY = 'redux';
class App extends Component {
constructor(props) {
super(props);
this.state = {
hits: [],
isLoading: false,
error: null,
};
}
componentDidMount(){
this.setState({ isLoading: true });
fetch(API + DEFAULT_QUERY)
.then(response => {
if (response.ok) {
return response.json();
} else {
throw new Error('Something went wrong with the API...');
}
})
.then(data => this.setState({ hits: data.hits[0], isLoading: false }))
.catch(error => this.setState({ error, isLoading: false }));
}
render() {
const { hits, isLoading, error } = this.state;
return (
<>
<button onClick={hits.url}>GENERATE</button>
</>
);
}
}
Please help me find out why my button doesn't work. And how do I iterate over the urls on each click, i.e. show the next url from the json on each click. Thanks.
You should pass a function name to your onClick handler. Then in that function you can access the data you wanted.
enter code here
import React, { Component } from 'react';
import { ThemeProvider, createToolkitTheme } from 'internaltools/theme';
import { AppHeader } from 'internaltools/app-header';
const LIGHT_THEME = createToolkitTheme('light');
const DARK_THEME = createToolkitTheme('dark');
const API = 'https://hn.algolia.com/api/v1/search?query=';
const DEFAULT_QUERY = 'redux';
class App extends Component {
constructor(props) {
super(props);
this.state = {
hits: [],
isLoading: false,
error: null,
hitsCount: 0
};
this.handleClick = this.handleClick.bind(this);
}
componentDidMount(){
this.setState({ isLoading: true });
fetch(API + DEFAULT_QUERY)
.then(response => {
if (response.ok) {
return response.json();
} else {
throw new Error('Something went wrong with the API...');
}
})
.then(data =>
this.setState({ hits: data.hits, hitsCount: 0 ,isLoading: false
}))
.catch(error => this.setState({ error, isLoading: false }));
}
handleClick(){
this.setState(prevState => ({ hitsCount: prevState.hitsCount + 1
}));
}
render() {
const { hits, hitsCount, isLoading, error } = this.state;
return (
<>
<div>
count: {hitsCount}
url: {hits[hitsCount].url}
</div>
<button onClick={this.handleClick}>GENERATE</button>
</>
);
}
}
You need to pass an onClick handler function to update a state value.
Here's a codesandbox that stores the hits array in state along with a current index, and a handler that simply increments the index.
Consider This:
Read through the comments in the code to get the updates.
class App extends Component {
constructor(props) {
super(props);
this.state = {
hits: [],
currentHit: 0, //add a state currentHit to hold the url that is displayed by now
isLoading: false,
error: null,
};
}
componentDidMount(){
this.setState({ isLoading: true });
fetch(API + DEFAULT_QUERY)
.then(response => {
if (response.ok) {
return response.json();
} else {
throw new Error('Something went wrong with the API...');
}
})
.then(data => this.setState({ hits: data.hits, isLoading: false })) //Make hits array holding all the hits in the response instead of only the first one
.catch(error => this.setState({ error, isLoading: false }));
}
handleClick = () => {
this.setState(prevState => ({
currentHit: prevState.currentHit + 1,
}));
}
render() {
const { hits, isLoading, error, currentHit } = this.state;
// pass the handleClick function as a callback for onClick event in the button.
return (
<>
<p>{hits[currentHit].url}<p/>
<button onClick={this.handleClick.bind(this)}>GENERATE</button>
</>
);
}
}
Here is the working code, on each click next url will be shown.
codesandbox link
handleChange method can work if you want to append the url from array as well. Or you could just increment the index in this function.
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
class App extends React.Component {
state = {
data: [],
index: 0
};
componentDidMount() {
this.setState({ isLoading: true });
fetch("https://reqres.in/api/users")
.then(response => {
if (response) {
return response.json();
} else {
throw new Error("Something went wrong with the API...");
}
})
.then(data => this.setState({ data: data.data }))
.catch(error => this.setState({ error }));
}
handleChange = () => {
let i =
this.state.index < this.state.data.length ? (this.state.index += 1) : 0;
this.setState({ index: i });
};
render() {
return (
<div className="App">
<span>
{this.state.data.length && this.state.data[this.state.index].avatar}
</span>
<button onClick={this.handleChange}>GENERATE</button>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
I want to get data using fetch() and pass it down my component hierarchy, and use that data to set the initial state of one of my components
I have tried setting the inital state using the props and passing them down.
componentDidMount = () => {
getFileSystem().then(response => {
if (response.success) {
this.setState({
filesystem: response.filesystem,
projects: response.projects
})
}
}).catch(err => {
this.setState({
filesystem: {
name: '/',
type: 'directory',
children: [
{ name: 'error.txt', type: 'file', data: 'error' }
]
},
projects: []
})
})
}
class TerminalContainer extends Component {
constructor(props) {
super(props)
this.state = {
filesystem: props.filesystem,
terminal_data: [''],
current_dir_name: '/',
current_dir: props.filesystem,
full_path: ""
}
}
...
But the component calls the constructor function before the data is loaded into the props of the component. This means that the inital state of the component is not set properly.
I need some way of preventing the component from being rendered until all of the data is ready
If you want to use the props given to a component as initial state, and these props are state in a parent component that are fetched asynchronously, you need to delay the rendering of the child component.
You could e.g. add an additional piece of state called isLoading that you set to false when the fetch is complete and use that to conditionally render the TerminalContainer component.
Example
class App extends React.Component {
state = {
isLoading: true,
filesystem: null,
projects: null
};
componentDidMount() {
getFileSystem()
.then(response => {
if (response.success) {
this.setState({
isLoading: false,
filesystem: response.filesystem,
projects: response.projects
});
}
})
.catch(err => {
this.setState({
isLoading: false,
filesystem: {
name: "/",
type: "directory",
children: [{ name: "error.txt", type: "file", data: "error" }]
},
projects: []
});
});
}
render() {
const { isLoading, filesystem } = this.state;
if (isLoading) {
return null;
}
return <TerminalContainer filesystem={filesystem} />;
}
}
Keep getting the following error message in React Native, really don't understand where it is coming from
Warning: Can't call setState (or forceUpdate) on an unmounted
component. This is a no-op, but it indicates a memory leak in your
application. To fix, cancel all subscriptions and asynchronous tasks
in the componentWillUnmount method.
I have the following simple component:
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
isLoggedIn: false,
}
}
componentDidMount(){
this.fetchToken()
}
async fetchToken(){
const access_token = await AsyncStorage.getItem('access_token')
if (access_token !== null) {
this.setState({ isLoggedIn: true })
}
}
render() {
const login = this.state.isLoggedIn
if (login) {
return <NavigatorLoggedIn />
} else {
return <Navigator/>
}
}
}
You can use it:
componentDidMount() {
this.function()
}
function = async () => {
const access_token = await AsyncStorage.getItem('access_token')
if (access_token !== null) {
this.setState({ isLoggedIn: true })
}
}
Or you can call function in constructor.
I hope this will help you...
It's will be work:
let self;
class App extends React.Component {
constructor(props) {
super(props)
self = this;
this.state = {
isLoggedIn: false,
}
}
componentDidMount(){
this.fetchToken()
}
async fetchToken(){
const access_token = await AsyncStorage.getItem('access_token')
if (access_token !== null) {
self.setState({ isLoggedIn: true })
}
}
render() {
const login = self.state.isLoggedIn
if (login) {
return <NavigatorLoggedIn />
} else {
return <Navigator/>
}
}
}
you need use isMounted variable.
componentDidMount(){
this.setState({ isMounted = true });
const access_token = await AsyncStorage.getItem('access_token')
if (access_token !== null && this.isMounted) {
this.setState({ isLoggedIn: true })
}
}
componentWillUnmount(){
this.setState({ isMounted = false });
}
Or if you use Axios, you can use cancel request feature of axios
this here: https://github.com/axios/axios#cancellation
You can try this:
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
isLoggedIn: false,
}
}
_isMounted = false;
componentDidMount(){
this._isMounted = true;
this.fetchToken()
}
async fetchToken(){
const access_token = await AsyncStorage.getItem('access_token')
if (access_token !== null && this._isMounted) {
this.setState({ isLoggedIn: true })
}
}
componentWillUnmount() {
this._isMounted = false;
}
render() {
const login = this.state.isLoggedIn
if (login) {
return <NavigatorLoggedIn />
} else {
return <Navigator/>
}
}
}
By using _isMounted, setState is called only if component is mounted, The unmounting doesn't wait for the async call to finish. Eventually when the async call gets over, the component is already unmounted and so it cannot set the state. To do this, the answer simply does a check to see if the component is mounted before setting the state.
Cancel all the async operation is one of the solution
For me, I resolved it by restart the server by "yarn start" or "npm start"