React class component not updating the state on Router - reactjs

class App extends Component {
constructor(props) {
super(props);
this.state = {
user: null,
userWorkSpecialIds: false,
};
}
isAuthorized = (user) => {
const { user } = this.state;
if (!user) return false;
else {
// check to see if user has special work permit id
this.hasWorkAccount(user);
return user.some((item) => user.email === item.id);
}
}
isLoaded = () => {
const { user } = this.state;
return user != null;
}
hasWorkAccount = () => {
getCustomerList(function(data) { // service gets users work place from firebase
const checkworkerSpecialIds = data.map((item) => {
return {
id: item.w_id,
name: item.w_name,
special_id: item.wspecial_id,
};
});
// checkworkerSpecialIds returns the user list of Ids correctly
console.log("checking for spacial_id : ", checkworkerSpecialIds);
// *** ERROR: ***
this.setState({ userWorkSpecialIds: !this.userWorkSpecialIds})
console.log("never reachs here when I have this.setState({}) ");
});
}
componentDidMount() {
const DataArray = [];
firebaseAppFirestore
.collection("users")
.get()
.then((snapshot) => {
snapshot.forEach((doc) => {
myDataArray.push({ id: doc.id, role: doc.role });
});
this.setState({ user: DataArray });
});
}
}
render() {
return (
<UserProvider>
<FirestoreProvider firebase={firebase}>
{this.isLoaded() ? (
this.isAuthorized(firebaseAppAuth.currentUser) ? (
<Router history={browserHistory}>
<Routes
users={this.state.users}
userWorkSpecialIds={this.state.userWorkSpecialIds} <----- Not updating after 1st render
/>
</Router>
) : (
<SignInView/>
)
) : (
<CircularProgress />
)}
</FirestoreProvider>
</UserProvider>
);
}
}
export default withFirebaseAuth({
firebaseAppAuth,
})(App);
I'm stuck on *** ERROR: ***, after I retrieve the checkworkerSpecialIds, should the
this.setState({ userWorkSpecialIds: !this.userWorkSpecialIds}), trigger the component and update userWorkSpecialIds.
I want to trigger the state userWorkSpecialIds updates and update the props
Currently the Routs props just receives does userWorkSpecialIds = false, never gets updated.

Related

React: can't update the state of Status, no re-render

I have been working with react for a few weeks and have encountered a problem when trying to create a todo app. When I want to update the state of status in handleStatus(), nothing happens in the browser.
handleStatus(event) {
let newStatus;
const changeState = event.status == 'done' ? newStatus = 'open' : newStatus = 'done';
this.setState({ status: newStatus });
Why I can't update this state like I did with the others? Does anyone have a solution?
Thank you very much.
Here is the full code:
import React from "react";
import { InputBar } from "./InputBar";
import { Todo } from "./Todo";
const emptyForm = {
enterTodo: ""
};
export class TodoTable extends React.Component {
constructor(props) {
super(props);
this.state = {
enterTodo: "",
todos: this.props.todos,
status: 'open'
};
this.handleEnterTodo = this.handleEnterTodo.bind(this);
this.handleStatus = this.handleStatus.bind(this);
this.handleCreateTodo = this.handleCreateTodo.bind(this);
this.handleClearTodos = this.handleClearTodos.bind(this);
this.handleDeleteTodo = this.handleDeleteTodo.bind(this);
}
//Textbox Input handler
handleEnterTodo(event) {
this.setState({
enterTodo: event.target.value
});
}
//Status handler
handleStatus(event) {
let newStatus;
const changeState = event.status == 'done' ? newStatus = 'open' : newStatus = 'done';
this.setState({ status: newStatus });
}
//delete todo
handleDeleteTodo(event) {
let todo = this.state.todos;
todo.splice(this.state.todos.indexOf(event), 1)
this.setState({ todo });
}
//Create Todo
handleCreateTodo(event) {
const todo = {
id: this.state.todos.length,
describtion: this.state.enterTodo,
status: 'open'
};
this.setState({
todos: [todo, ...this.state.todos]
})
this.state.enterTodo = emptyForm.enterTodo; // Überarbeiten
}
//Clear Todo List
handleClearTodos(event) {
let CleanedTodos = []
this.state.todos.forEach((element, index) => {
if(this.state.todos[index].status == 'open'){
CleanedTodos.push(this.state.todos[index]);
}
});
this.setState({
todos: CleanedTodos
});
}
render() {
return (
<>
<InputBar
handleCreateTodo={ this.handleCreateTodo }
handleEnterTodo={ this.handleEnterTodo }
enterTodo={ this.state.enterTodo }
handleClearTodos={ this.handleClearTodos }
/>
<Todo
handleStatus={ this.handleStatus }
todos={ this.state.todos }
handleClearTodos={ this.state.handleClearTodos }
handleDeleteTodo= { this.handleDeleteTodo }
/>
</>
);
}
}
Remember if your are keeping the status of done in this component then all the todos in the todo table will show they are of the status done
the best way would be each todo having its own local state of done so that each done state is unique and then handle it something like this
<p value="heyy" onClick={(e) => console.log(e.target.getAttribute("value"))}>Something</p>
OR
const Todo = (props) => {
const [done, setDone] = useState(false)
return <p onclick={() => setDone(true)} >{todo}</p>
}
todos.map(todo => {
return <Todo />
})

How to add page number to the URL

Could someone please tell me how can I add page number to my url. The component is as follows:
/** NPM Packages */
import React, { Component } from "react";
import { connect } from "react-redux";
import { Spinner, Pagination } from "react-bootstrap";
//import styles from "./App.module.css";
/** Custom Packages */
import List from "../List";
//import fetchCategories from "../../../actions/configuration/category/fetchCategories";
import deleteCategory from "../../../actions/configuration/category/deleteCategory";
import API from "../../../../app/pages/utils/api";
class Category extends Component {
constructor(props) {
super(props);
this.state = {
mesg: "",
mesgType: "",
isLoading: true,
total: null,
per_page: null,
current_page: 1,
pdata: []
};
this.fetchCategoriesAPI = this.fetchCategoriesAPI.bind(this);
}
fetchCategoriesAPI = async pno => {
await API.get("categories?offset=" + (pno.index+1))
.then(res => this.setState({ pdata: res.data }))
.then(() => this.props.passToRedux(this.state.pdata))
.catch(err => console.log(err));
};
componentDidMount = async () => {
const { state } = this.props.location;
if (state && state.mesg) {
this.setState({
mesg: this.props.location.state.mesg,
mesgType: this.props.location.state.mesgType
});
const stateCopy = { ...state };
delete stateCopy.mesg;
this.props.history.replace({ state: stateCopy });
}
this.closeMesg();
await this.fetchCategoriesAPI(1);
this.setState({ isLoading: false });
};
onDelete = async id => {
this.props.removeCategory(id);
await deleteCategory(id).then(data =>
this.setState({ mesg: data.msg, mesgType: "success" })
);
this.closeMesg();
};
closeMesg = () =>
setTimeout(
function() {
this.setState({ mesg: "", mesgType: "" });
}.bind(this),
10000
);
/** Rendering the Template */
render() {
let activePage = this.state.pdata.currPage;
let items = [];
let totalPages = Math.ceil(this.state.pdata.totalCount / 10);
for (let number = 1; number <= totalPages; number++) {
items.push(
<Pagination.Item key={number} active={number == activePage}>
{number}
</Pagination.Item>
);
}
const paginationBasic = (
<div>
<Pagination>
{items.map((item,index)=>{
return <p key={index} onClick={() => this.fetchCategoriesAPI({index})}>{item}</p>
})}
</Pagination>
<br />
</div>
);
const { mesg, mesgType, isLoading } = this.state;
return (
<>
{mesg ? (
<div
className={"alert alert-" + mesgType + " text-white mb-3"}
role="alert"
>
{mesg}
</div>
) : (
""
)}
{isLoading ? (
<div className="container-fluid">
<h4
className="panel-body"
style={{ "text-align": "center", margin: "auto" }}
>
Loading
<Spinner animation="border" role="status" />
</h4>
</div>
) : (
<div>
<List
listData={this.props.categories}
listName="category"
_handleDelete={this.onDelete.bind(this)}
/>
{paginationBasic}
</div>
)}
</>
);
}
}
const matchStatestoProps = state => {
return { categories: state.categories };
};
const dispatchStatestoProps = dispatch => {
return {
passToRedux: pload =>
dispatch({ type: "FETCH_CATEGORIES", payload: pload }),
removeCategory: id => dispatch({ type: "DELETE_CATEGORY", payload: id })
};
};
export default connect(matchStatestoProps, dispatchStatestoProps)(Category);
the route is as follows:
<Route exact path="/categories/:page?" component={Category} />
So basically I want the page number to be displayed in the URL. Also if I change the page number, the data should load the corresponding page. Please help me
Could someone please help me out?
In a class component:
Your router will pass match in as a prop. When your component mounts, get this.props.match.params.page and load the data accordingly:
class MyComponent extends React.Component {
componentDidMount () {
// get the 'page' param out of the router props.
// default to 0 if not specified.
const { page = 0 } = this.props.match.params;
// it comes in as a string, parse to int
const p = parseInt(page, 10);
// do whatever you need to do (load data, etc.)
}
}
In a function component:
In a function component, you can get the page param via react-router's useParams hook:
import { useParams } from 'react-router-dom';
function MyComponent () {
const { page } = useParams(); // get the 'page' router param
const p = parseInt(page, 10); // comes in as a string, convert to int
// do whatever you need to do with it
}
If you need prev/next navigation you can deduce those page numbers from the current page.
I made this quick example that demonstrates how to access and use the route's url parameters via react router's useParams hook and how to do it via the match prop with a class component.
You can get page number from props like this:
const matchStatestoProps = (state, ownProps) => {
return { id: ownProps.match.params.id; categories: state.categories };
};
In your routes:
<Route path="/page/:id" component={Page} />

How to stop react component from infinite re-render

I wanna use js-cookie in my app, but the time I get the cookie my component keeps re-rendering until the browser crash.
The error I get is: setState(...): Cannot update during an existing state transition ...
I've just used the shouldComponentUpdate but it caused the click events not working.
shouldComponentUpdate(nextProps, nextState)
{
return nextState.language != this.state.language;
}
Does anybody know any other solution rather than shouldComponentUpdate to stop a component from infinite re-render?
class MainLayout extends Component {
constructor(props) {
super(props);
console.log('constructor');
this.state = {
sideBarOpen: false,
languages: getStoreLanguages,
language: Cookies.get('langCode')
}
}
componentWillMount() {
this.props.langCode();
this.props.defaultLangCode();
}
componentDidMount() {
$('.dropdown-toggle').megaMenu && $('.dropdown-toggle').megaMenu({ container: '.mmd' });
}
shouldComponentUpdate(nextProps, nextState) {
return nextState.language != this.state.language;
}
toggleSidebar = () => {
this.setState({
sideBarOpen: !this.state.sideBarOpen,
});
}
overlayClickHandler = () => {
this.setState({
sideBarOpen: false,
});
}
handleLanguage = (langCode) => {
if (Cookies.get('langCode')) {
return Cookies.get('langCode');
} else {
Cookies.set('langCode', langCode, { expires: 7 });
return langCode;
}
}
render() {
let overlay = { display: this.state.sideBarOpen ? 'block' : 'none' };
const langCode = this.handleLanguage(this.props.params.id);
const isDefaultLang = isDefaultLanguage(langCode);
const isValidLang = isValidLanguage(langCode);
if (langCode && !isValidLang) {
this.props.router.push(`/${langCode}/error`);
}
if (langCode && isValidLang) {
const path = getLocalisedPath(langCode, isDefaultLang)
this.props.router.push("/" + path);
}
return (
<div>
<Helmet>
<script type="application/ld+json">{structuredData()}</script>
</Helmet>
<TokenManager>
{(state, methods) => (
<div>
<WithHelmet {...this.props} />
<WithUserHeaderInfo {...this.props} />
<WithStoreDetail />
<WithDataLayerStoreDetail />
<header className='header__nav'>
<NavbarManagerWillHideOnEditor
sideBarOpen={this.state.sideBarOpen}
toggleSidebar={this.toggleSidebar}
languages={this.state.languages}
{...this.props}
/>
<div
className="mmm__overlay"
onClick={this.overlayClickHandler}
style={overlay}
/>
<div
className="mmm__overlay--hidenav"
onClick={this.overlayClickHandler}
style={overlay}
/>
</header>
<main>{this.props.children}</main>
<Modal modalId="modal-account" size="md">
{(closeModal) => (
<Auth
closeModal={closeModal} />
)}
</Modal>
{!this.props.location.pathname.startsWith('/checkout') && <FooterWillHideOnEditor languages={this.state.languages}/>}
</div>
)
}
</TokenManager>
</div>
);
}
}
const mapDispatchToProps = (dispatch, value) => {
const langCode = Cookies.get('langCode') || value.params.id;
const defaultLang = getDefaultLanguage();
const isDefault = isDefaultLanguage(langCode);
const isValid = isValidLanguage(langCode);
const lang = !isValid || isDefault ? defaultLang : langCode;
return {
langCode: () => dispatch({ type: 'SET_LANGUAGE', payload: lang }),
defaultLangCode: () => dispatch({ type: 'SET_DEFAULT_LANGUAGE', payload: defaultLang })
}
}
export default connect(null, mapDispatchToProps)(MainLayout);
let overlay = { display: this.state.sideBarOpen ? 'block' : 'none' };
const langCode = this.handleLanguage(this.props.params.id);
const isDefaultLang = isDefaultLanguage(langCode);
const isValidLang = isValidLanguage(langCode);
if (langCode && !isValidLang) {
this.props.router.push(`/${langCode}/error`);
}
if (langCode && isValidLang) {
const path = getLocalisedPath(langCode, isDefaultLang)
this.props.router.push("/" + path);
}
Here the code is resetting the state/ or mapDispatchToProps dispatched again .that's why it's rerendering.
I found the reason why component keep re-rendering, actually it was because of the condition in:
if (langCode && isValidLang) {
const path = getLocalisedPath(langCode, isDefaultLang)
this.props.router.push("/" + path);
}
which is always true and gets the path and push to the route which cause the component re-render.
Thanks

Firebase/React/Redux Component has weird updating behavior, state should be ok

I am having a chat web app which is connected to firebase.
When I refresh the page the lastMessage is loaded (as the gif shows), however, for some reason, if the component is otherwise mounted the lastMessage sometimes flickers and disappears afterwards like it is overridden. When I hover over it, and hence update the component, the lastMessage is there.
This is a weird behavior and I spent now days trying different things.
I would be very grateful if someone could take a look as I am really stuck here.
The db setup is that on firestore the chat collection has a sub-collection messages.
App.js
// render property doesn't re-mount the MainContainer on navigation
const MainRoute = ({ component: Component, ...rest }) => (
<Route
{...rest}
render={props => (
<MainContainer>
<Component {...props} />
</MainContainer>
)}
/>
);
render() {
return (
...
<MainRoute
path="/chats/one_to_one"
exact
component={OneToOneChatContainer}
/>
// on refresh the firebase user info is retrieved again
class MainContainer extends Component {
componentDidMount() {
const { user, getUserInfo, firebaseAuthRefresh } = this.props;
const { isAuthenticated } = user;
if (isAuthenticated) {
getUserInfo(user.id);
firebaseAuthRefresh();
} else {
history.push("/sign_in");
}
}
render() {
return (
<div>
<Navigation {...this.props} />
<Main {...this.props} />
</div>
);
}
}
Action
// if I set a timeout around fetchResidentsForChat this delay will make the lastMessage appear...so I must have screwed up the state / updating somewhere.
const firebaseAuthRefresh = () => dispatch => {
firebaseApp.auth().onAuthStateChanged(user => {
if (user) {
localStorage.setItem("firebaseUid", user.uid);
dispatch(setFirebaseAuthUser({uid: user.uid, email: user.email}))
dispatch(fetchAllFirebaseData(user.projectId));
}
});
};
export const fetchAllFirebaseData = projectId => dispatch => {
const userId = localStorage.getItem("firebaseId");
if (userId) {
dispatch(fetchOneToOneChat(userId));
}
if (projectId) {
// setTimeout(() => {
dispatch(fetchResidentsForChat(projectId));
// }, 100);
...
export const fetchOneToOneChat = userId => dispatch => {
dispatch(requestOneToOneChat());
database
.collection("chat")
.where("userId", "==", userId)
.orderBy("updated_at", "desc")
.onSnapshot(querySnapshot => {
let oneToOne = [];
querySnapshot.forEach(doc => {
let messages = [];
doc.ref
.collection("messages")
.orderBy("created_at")
.onSnapshot(snapshot => {
snapshot.forEach(message => {
messages.push({ id: message.id, ...message.data() });
});
});
oneToOne.push(Object.assign({}, doc.data(), { messages: messages }));
});
dispatch(fetchOneToOneSuccess(oneToOne));
});
};
Reducer
const initialState = {
residents: [],
oneToOne: []
};
function firebaseChat(state = initialState, action) {
switch (action.type) {
case FETCH_RESIDENT_SUCCESS:
return {
...state,
residents: action.payload,
isLoading: false
};
case FETCH_ONE_TO_ONE_CHAT_SUCCESS:
return {
...state,
oneToOne: action.payload,
isLoading: false
};
...
Main.js
// ...
render() {
return (...
<div>{React.cloneElement(children, this.props)}</div>
)
}
OneToOne Chat Container
// without firebaseAuthRefresh I don't get any chat displayed. Actually I thought having it inside MainContainer would be sufficient and subscribe here only to the chat data with fetchOneToOneChat.
// Maybe someone has a better idea or point me in another direction.
class OneToOneChatContainer extends Component {
componentDidMount() {
const { firebaseAuthRefresh, firebaseData, fetchOneToOneChat } = this.props;
const { user } = firebaseData;
firebaseAuthRefresh();
fetchOneToOneChat(user.id || localStorage.getItem("firebaseId"));
}
render() {
return (
<OneToOneChat {...this.props} />
);
}
}
export default class OneToOneChat extends Component {
render() {
<MessageNavigation
firebaseChat={firebaseChat}
firebaseData={firebaseData}
residents={firebaseChat.residents}
onClick={this.selectUser}
selectedUserId={selectedUser && selectedUser.residentId}
/>
}
}
export default class MessageNavigation extends Component {
render() {
const {
onClick,
selectedUserId,
firebaseChat,
firebaseData
} = this.props;
<RenderResidentsChatNavigation
searchChat={this.searchChat}
residents={residents}
onClick={onClick}
firebaseData={firebaseData}
firebaseChat={firebaseChat}
selectedUserId={selectedUserId}
/>
}
}
const RenderResidentsChatNavigation = ({
residents,
searchChat,
selectedUserId,
onClick,
firebaseData,
firebaseChat
}) => (
<div>
{firebaseChat.oneToOne.map(chat => {
const user = residents.find(
resident => chat.residentId === resident.residentId
);
const selected = selectedUserId == chat.residentId;
if (!!user) {
return (
<MessageNavigationItem
id={chat.residentId}
key={chat.residentId}
chat={chat}
onClick={onClick}
selected={selected}
user={user}
firebaseData={firebaseData}
/>
);
}
})}
{residents.map(user => {
const selected = selectedUserId == user.residentId;
const chat = firebaseChat.oneToOne.find(
chat => chat.residentId === user.residentId
);
if (_isEmpty(chat)) {
return (
<MessageNavigationItem
id={user.residentId}
key={user.residentId}
chat={chat}
onClick={onClick}
selected={selected}
user={user}
firebaseData={firebaseData}
/>
);
}
})}
</div>
}
}
And lastly the item where the lastMessage is actually displayed
export default class MessageNavigationItem extends Component {
render() {
const { hovered } = this.state;
const { user, selected, chat, isGroupChat, group, id } = this.props;
const { messages } = chat;
const item = isGroupChat ? group : user;
const lastMessage = _last(messages);
return (
<div>
{`${user.firstName} (${user.unit})`}
{lastMessage && lastMessage.content}
</div>
)
}
In the end it was an async setup issue.
In the action 'messages' are a sub-collection of the collection 'chats'.
To retrieve them it is an async operation.
When I returned a Promise for the messages of each chat and awaited for it before I run the success dispatch function, the messages are shown as expected.

React Native Navigation / Context API

I'm using React-Native-Navigation with the Context api.
I'm wrapping my screens with a HOC component below.
const ProviderWrap = Comp => props => (
<Provider>
<Comp {...props} />
</Provider>
);
Navigation.registerComponent('app.AuthScreen', () => ProviderWrap(AuthScreen));
Navigation.registerComponent('app.FindPlaceScreen', () => ProviderWrap(FindPlaceScreen));
Navigation.registerComponent('app.SharePlaceScreen', () => ProviderWrap(SharePlaceScreen));
And this is my Provider Component
class Provider extends Component {
state = {
placeName: 'hello',
image: 'https://images.unsplash.com/photo-1534075786808-9b819c51f0b7?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=4dec0c0b139fb398b714a5c8264b4a9a&auto=format&fit=crop&w=934&q=80',
places: [],
}
textInputHandler = (text) => {
this.setState({
placeName: text,
})
}
searchInputHandler = (text) => {
const SearchedPlace = this.state.places.filter(place => {
return place.name.includes(text)
})
this.setState(prevState => {
return {
places: SearchedPlace,
}
})
}
placeSubmitHandler = () => {
if (this.state.placeName) {
this.setState(prevState => {
return {
places: [...prevState.places, {
name: prevState.placeName,
image: prevState.image,
}],
placeName: '',
}
})
} else {
return;
}
}
placeDeleteHandler = (id, deleteModal) => {
const newPlaces = this.state.places.filter((place, index) => {
return index !== id
})
this.setState({
places: newPlaces,
})
deleteModal();
}
render() {
return (
<GlobalContext.Provider value={{
state: this.state,
textInputHandler: this.textInputHandler,
placeSubmitHandler: this.placeSubmitHandler,
placeDeleteHandler: this.placeDeleteHandler,
searchInputHandler: this.searchInputHandler,
}}>
{this.props.children}
</GlobalContext.Provider>
)
}
}
My issue is that The Context state isnt shared between screens. in my context state i have a places array which i add to in the SharePlaceScreen which works fine but when i go over to FindPlaceScreen that context state places is empty. As if i had two separate context for each screen.
Here is an example of singleton Object there are many implementation you cas use es6 class also ... es6 examples
var SingletonState = (function () {
var state;
function createState() {
return {someKey:'what ever'};
}
return {
getState: function () {
if (!state) {
state = createState();
}
return state;
}
};
})();
// usage
var state1 = SingletonState.getState();
var state2 = SingletonState.getState();
console.log("Same state? " + (state1 === state2));

Resources