I'm training React Hooks with a movie app and I now face with a problem. When I click in a button in the Header component, there should be a change of state by cleaning the itemsList array. The code is actually prepared for a Load More button, that will add more items to this array, when the API request used with other component (not present yet).
The problem is that the array is not been cleaned and when some button is cliche the items from que API Request are added to it.
This is the App.js file
import React, { useState } from "react";
import axios from "axios";
import Header from "./Containers/Header";
export default function App() {
const [values, setValues] = useState({
msdb: "API_CODE",
page: 1,
totalPages: 0,
listType: "popular",
mode: "movie",
itemsList: [],
searchFiled: "",
loading: false,
error: false
});
const { msdb, page, totalPages, listType, mode, itemsList, searchFiled, loading, error } = values;
const modeSelection = (event) => {
let modeType = "";
let selectedMode = event.target.innerText;
console.log(selectedMode);
if (selectedMode === "Movies") {
modeType = "movie";
} else if (selectedMode === "Series") {
modeType = "tv";
}
setValues((prevValues) => {
return { ...prevValues, mode: modeType, itemsList: [] };
});
let endPoint = `https://api.themoviedb.org/3/${mode}/${listType}?api_key=${msdb}&page=${page}`;
fetchItems(endPoint);
};
const fetchItems = (endPoint) => {
axios
.get(endPoint)
.then((response) => {
const newItemsList = [...itemsList];
const newItems = response.data.results;
if (newItems) {
setValues((prevValues) => {
return {
...prevValues,
page: response.data.page,
itemsList: [...newItemsList, ...newItems],
loading: false,
totalPages: response.data.total_pages
};
});
}
})
.catch((error) => {
setValues({ ...values, error: true });
console.log(error);
});
};
return (
<div className="App">
<Header mode={modeSelection} />
</div>
);
}
And this is the Header.js file
import React from "react";
import "./Header.css";
import { NavLink } from "react-router-dom";
export default function Header(props) {
return (
<div>
<div className="top-center">
<NavLink to="/movies" onClick={props.mode} className="ch-button">
Movies
</NavLink>
<NavLink to="/series" onClick={props.mode} className="ch-button">
Series
</NavLink>
</div>
</div>
);
}
So what I would like to be the result is, when clicking on Header component buttons, the itemsList array should be cleaned and the API request would populate it again. Remember that the axios method is already prepared for a Load More Button in another component and, in this case, it will add more Items to the array. Should there be a useEffect somewhere?
Thank you
The problem is about the asynchronous nature of setState. You correctly use prevState to set a new one, but in the 'fetchItems' when setting new items, you get old ones from current state and not prevState. This way the state is not yet updated with empty array when you use it for setting new items. You can try
if (newItems) {
setValues((prevValues) => {
return {
...prevValues,
page: response.data.page,
itemsList: [...prevState.itemsList, ...newItems],
loading: false,
totalPages: response.data.total_pages
};
});
}
Related
I need to get the data from the server and save it somewhere so that after re-rendering the LoginsList component, I don't have to request the data from the server again. I decided to start using MobX, but the store function fetchData() just doesn't seem to get called.
For now, the data is accepted in raw form, but then I will use encryption.
store.js
import { makeObservable } from 'mobx';
class store {
data = []
isFetching = false
error = null
constructor() {
makeObservable(this, {
data: observable,
isFetching: observable,
error: observable,
fetchData: action
})
}
fetchData() {
this.isFetching = true
axios.get('http://localhost:3001/data')
.then(response => {
this.data = response.data
this.isFetching = false
console.log('Success');
})
.catch(err => {
this.error = err
this.isFetching = false
console.log('Error');
})
}
}
export default store;
LoginsList.js
import React, { useState, useEffect } from 'react';
import classNames from 'classnames';
import { Observer } from 'mobx-react-lite';
import store from '../.store/data';
import LoginDetails from './LoginDetails';
import Login_Icon from '../assets/icons/Login.svg'
import '../assets/css/LoginCards.css'
const LoginsList = () => {
const [activeTab, setActiveTab] = useState(0);
const [hoveredTab, setHoveredTab] = useState(null);
const handleMouseEnter = (index) => {
if (index !== activeTab) {
setHoveredTab(index);
}
}
const handleClick = (index) => {
setHoveredTab(null);
setActiveTab(index);
}
useEffect(() => {
store.fetchData();
}, []);
return (
<>
<Observer>
<ul>
{store.data.map((card, index) => (
<li
key={card.id}
onClick={() => handleClick(index)}
onMouseEnter={() => handleMouseEnter(index)}
onMouseLeave={() => setHoveredTab(null)}
className="LoginCard"
>
<div
className={classNames('LoginCardContainer', { 'active-logincard': index === activeTab }, { 'hovered-logincard': index === hoveredTab })}
>
<img src={Login_Icon} alt="Login Icon"></img>
<div className="TextZone">
<p>{card.name}</p>
<div>{card.username}</div>
</div>
</div>
</li>
))}
</ul>
<div>
<div className="LoginDetails">
<img className="LoginDetailsIcon" src={Login_Icon}></img>
</div>
<LoginDetails key={activeTab} name={store.data[activeTab].name} username={store.data[activeTab].username} password={store.data[activeTab].password}/>
{store.data[activeTab].password}
</div>
</Observer>
</>
);
}
export default LoginsList;
I tried creating a store, importing it into the LoginsList component and getting the data. In the browser console, I saw an error Uncaught TypeError: _store_data__WEBPACK_IMPORTED_MODULE_3__.default.data is undefined
If I open http://localhost:3001/data in my browser, I can easily read the data. I don't think the error is on the server side.
I solved the problem. All I had to do was use makeAutoObservable instead of makeObservable.
import { action, makeAutoObservable } from 'mobx';
import axios from 'axios';
class UserData {
data = []
isFetching = false
error = null
constructor() {
makeAutoObservable(this)
}
fetchData() {
this.isFetching = true
axios.get('http://localhost:3001/data')
.then(response => {
this.data = response.data
this.isFetching = false
console.log('Success');
})
.catch(err => {
this.error = err
this.isFetching = false
console.log('Error');
})
};
}
export default new UserData;
Ok so I am trying to understand React Hooks and how to update
my code to grab the JSON from the source below and show the data. I'm clear on importing the hook and initializing it with useState(0) but my code fails when I try to re-factor within my fetch statement. Any/all help would be greatly appreciated...see below.
// import React, { Component } from 'react';
import React, { useState } from 'react';
import Feeder from './Feeder';
import Error from './Error';
// class NewsFeeder extends Component {
// constructor(props) {
// super(props);
// this.state = {
// news: [],
// error: false,
// };
// }
const [hideNews,showNews] = useState(0);
componentDidMount() {
const url = `https://newsfeed.com`;
fetch(url)
.then((response) => {
return response.json();
})
.then((data) => {
this.setState({
news: data.articles
})
})
.catch((error) => {
this.setState({
error: true
})
});
}
renderItems() {
if (!this.state.error) {
return this.state.news.map((item) => (
<FeedPrime key={item.url} item={item} />
));
} else {
return <Error />
}
}
render() {
return (
<div className="row">
{this.renderItems()}
</div>
);
}
}
export default NewsFeeder;
React hooks are created for functional components and are not ment to be used in class components.
Here is a table of the functionality and the way to achive it using classes and functions with hooks.
component type
state
fetch
class
store the state in this.state that you only assign once in the constructor, use this.setState to modify the state
do your fetch logic in componentDidMount
function
create a pair of [example, setExample] with useState
do fetch in useEffect hook
Using fetch with hooks: (edited version of this):
import React, { useState, useEffect } from 'react';
function App() {
const [data, setData] = useState({ hits: [] });
useEffect(async () => {
const result = await fetch('https://hn.algolia.com/api/v1/search?query=redux').then(response => response.json());
setData(result);
});
let items = data.hits.map(item => (
<li key={item.objectID}>
<a href={item.url}>{item.title}</a>
</li>
));
return (
<ul>
{items}
</ul>
);
}
export default App;
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>
I use axios to create my datatatable using MUIDataTable in my React JS. but with my code the result is just show empty table..
in code below i dont know where i should change, because the result is empty table without data result as in JSON.why const data cant be read, anyone can help?
here's my code
App.js
import React,{Component} from "react";
import ReactDOM from "react-dom";
import MUIDataTable from "mui-datatables";
import axios from "axios";
import { Link } from 'react-router-dom';
class App extends Component {
// State will apply to the posts object which is set to loading by default
constructor(props) {
super(props);
this.state = {
data: [],
isLoading: true,
errors: null,
};
}
// Now we're going to make a request for data using axios
getData = async () => {
const option = {
url: "url/api",
method: 'POST',
data: {
"data": {
"name": "...",
"id":"..."
},
"encrypt": 0
}
};
axios(option)
.then(response => {
this.setState({
data: response.data.data,
isLoading: false,
});
console.log(response.data);
})
// If we catch any errors connecting, let's update accordingly
.catch(error => {
console.log(error.response);
this.setState({ error, isLoading: false })
}
);
}
// Let's our app know we're ready to render the data
componentDidMount() {
this.getData();
}
// Putting that data to use
render() {
const { isLoading, data } = this.state;
const columns = ["ID","Name];
const options = {
filterType: "dropdown",
responsive: "scroll",
selectableRows:false
};
return (
<div>
<center><h3>List Data</h3></center><br/>
<MUIDataTable
columns={columns}
options={options}
data={data}
/>
{!isLoading ? (
data.map(post => {
const {id, name} = post;
const data = [
[
{id},
{name}
})
) : (
<p>Loading...</p>
)}
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
export default App
Your option is wrong. url should be outside of the option. the correct order is:
axios.post('url' , options , headers).This may be helpful.For more info please visit https://github.com/axios/axios.
So, once again, I've been facing this issue of persisting the state tree. In login, for the user to persist, I dispatched an action from my main App.js and got the current logged in user like this:
App.js
componentDidMount() {
const authToken = localStorage.getItem("authToken")
if (authToken) {
this.props.dispatch({ type: "TOKEN_VERIFICATION_STARTS" })
this.props.dispatch(getCurrentUser(authToken))
}
}
Now, I have a form and when it is submitted I'm redirecting the user to the feed where I will show the post title, description in a card form. But as usual, the postData is disappearing after refresh.
It means do I have to make another route, similar to the /me route that I made for getting the current logged in user? And dispatch an action again from the componentDidMount() in App.js?
NewPostForm.js
import React, { Component } from "react"
import { connect } from "react-redux"
import { addpost } from "../actions/userActions"
class NewpostForm extends Component {
constructor(props) {
super(props)
this.state = {
postTitle: "",
postDescription: "",
maxLength: 140
}
}
handleChange = (event) => {
const { name, value } = event.target
this.setState({
[name]: value
})
}
handleSubmit = () => {
const postData = this.state
this.props.dispatch(addpost(postData, () => {
this.props.history.push("/feed")
})
)
}
render() {
const charactersRemaining = (this.state.maxLength - this.state.postDescription.length)
return (
<div>
<input
onChange={this.handleChange}
name="postTitle"
value={this.state.postTitle}
className="input"
placeholder="Title"
maxLength="100"
/>
<textarea
onChange={this.handleChange}
name="postDescription"
value={this.state.postDescription}
className="textarea"
maxLength="140">
</textarea>
<button onClick={this.handleSubmit}>Submit</button>
<div>
Characters remaining: {charactersRemaining}
</div>
</div>
)
}
}
const mapStateToProps = (store) => {
return store
}
export default connect(mapStateToProps)(NewpostForm)
addPost action
export const addpost = (postData, redirect) => {
console.log("inside addpost action")
return async dispatch => {
dispatch({
type: "ADD_post_STARTS"
})
try {
const res = await axios.post("http://localhost:3000/api/v1/posts/new", postData, {
headers: {
"Content-Type": "application/json",
"Authorization": `${localStorage.authToken}`
}
})
dispatch({
type: "ADD_post_SUCCESS",
data: { post: res.data.post },
})
redirect()
} catch (err) {
dispatch({
type: "ADD_post_ERROR",
data: { error: "Something went wrong" }
})
}
}
}
Feed.js
import React from "react";
import { connect } from "react-redux";
const Feed = (props) => {
// const postTitle = (props.post && props.post.post.post.postTitle)
return (
<div className="card">
<header className="card-header">
<p className="card-header-title">
{/* {postTitle} */}
</p>
</header>
<div className="card-content">
<div className="content">
The text of the post written by the user.
</div>
</div>
<footer className="card-footer">
<a href="#" className="card-footer-item">
Edit
</a>
<a href="#" className="card-footer-item">
Delete
</a>
</footer>
</div>
);
};
const mapStateToProps = state => {
return state;
};
export default connect(mapStateToProps)(Feed);
I know you want without redux-persist but the redux normal behavior force to initialize store again from scratch. If you want to persist your state even refresh your page, I would recommend the following package:
https://github.com/rt2zz/redux-persist
If you are losing your state on a page redirect or traveling to a different route using react-router you will want to use:
https://github.com/reactjs/react-router-redux
If I understand correctly it looks like you are using response of /api/v1/posts/new in your feed page however trying to access local state of NewPostForm.js
this.state = {
postTitle: "",
postDescription: "",
maxLength: 140
}
Instead of using local state to save form data which cannot be shared to another component(unless passed as props which is not the case here) you may need to save data to redux store so that it can be shared across different route
handleChange = (event) => {
const { dispatch } = this.props;
const { name, value } = event.target;
dispatch(setPostData(name, value));
}
You action may look like:-
export const setPostData = (name, value) => ({
type: "SET_POST_DATA",
name,
value,
});
After that you can use this.props.postTitle on feed page
Edit: in order to keep state between page reload (full browser reload), you may need to either fetch all data on mount(higher order components are helpful) or use local storage.