React render using state-pool - javascript

Standard JS file:
// functions.js
import cockpit from "cockpit";
import { store } from "state-pool";
const global_status = {
key1: null,
key2: null,
}
store.setState("status", global_status);
function update_config(data, message) {
const json_data = JSON.parse(data);
store.setState("status", {
key1: json_data.key1,
key2: json_data.key2,
};)
}
export function get_config() {
cockpit.spawn(python_script, superuser: "try", err: "message" })
.done(function (data, message) { update_config(data, message) })
.fail(function (error) { console.log(["spawn() failed: ", error]) });
}
// Initial loading of values
get_config()
In my JSX file I have this:
// app.jsx
import React, { useEffect } from 'react';
import { Alert, Card, CardTitle, CardBody } from '#patternfly/react-core';
import { store, useGlobalState } from 'state-pool';
function ShowStatus () {
const stat = store.getState("status");
const info = stat.key1 ? "info" : !stat.key1 ? stat.installed ? "error" : "warning";
const status = stat.key2 ? "Enabled" : !stat.key2 ? "Available" : "Unkown";
return (
<Alert variant={info} title={status} />
);
}
export function App () {
const [status] = useGlobalState('status');
useEffect(
() => ShowStatus, [status]
);
return (
<Card>
<CardTitle>Status</CardTitle>
<CardBody>
<ShowStatus />
</CardBody>
</Card>
);
}
When rendering this in Fedora Cockpit, it works, but only the initial loading of values (key1: null, key2: null) show. After get_config() is called, the status page never updates.
I've used console.log() and verified that "status" gets updated after get_config() is called, but the page does not render with the newer data.
CAVEAT: It's been some time since I coded in JS, and this is the first time I'm using React.
I've read the React docs as well as about 30 answers from stackoverflow about this, but I still have some issues getting around some of the Reactisms.
Any help would be greatly appreciated.
update:
One of the variations I've tried:
// app.jsx
function ShowStatus (stat) {
const info = stat.key1 ? "info" : !stat.key1 ? stat.installed ?
...
}
export function App () {
...
<CardBody>
<ShowStatus stat={status} />
</CardBody>
...
Reason for putting get_config() in a separate file is due to several different pages may end up trying to update the configuration setup.

I had similar issue... trying to recreate what you've done here, I believe you have to "subscribe" to the keys you want to have update your render function.
From the github readme:
store.subscribe & globalState.subscribe
If you want to listen to changes in a store you can subscribe to it by using
store.subscribe. it accepts an observer function. For example
// Subscribe to store changes
const unsubscribe = store.subscribe(function(key: String, value: Any){
// key is the key for a global state that has changed
// value is the new value of a global state
})
// You can unsubscribe by calling the result
unsubscribe();
Here is how it is accomplished in the test file:
import React from 'react';
import { renderHook, act } from '#testing-library/react-hooks';
import { createStore } from '../src/';
const store = createStore();
store.setState("count", 0);
let testVal1 = 0;
let testVal2 = 0;
test('should update testVal1 & testVal2 through subscribers', () => {
const { result } = renderHook(() => store.useState("count"))
act(() => {
store.subscribe((key, value) => {
testVal1 = 1
})
store.getState("count").subscribe((value) => {
testVal2 = 2
})
result.current[2](count => 1)
})
expect(testVal1).toStrictEqual(1);
expect(testVal2).toStrictEqual(2);
})

Related

state is not getting updated for a new user in react native

My app has a test sheet, if a user passes the test he is shown a passing screen and then the state is saved using asyncstorage. But the problem here is, let's say i have user A and user B and user A is currently logged in, he passed the test and the app shows him passing screen and the state is saved. Now user A logs out and user B logs in, he is a completely new user he has never given test before but my app has still saved the state for the user A and keeps showing passing screen even to the user B rather it should not.Can someone help me with this issue?
code:
import React ,{useState, useEffect} from "react";
import {View, Alert, Image, StyleSheet, Text, Modal, TouchableOpacity, TouchableHighlight} from 'react-native';
import Voice from 'react-native-voice';
import auth from '#react-native-firebase/auth';
import AsyncStorage from '#react-native-async-storage/async-storage';
const key = auth().currentUser.uid + "hasPassed"
export const hasPassed = async () => {
return AsyncStorage.getItem(key).then(result => result != null ? JSON.parse(result) : undefined).catch(e => console.log(e))
}
export const setHasPassed = async (newPassed) => {
return AsyncStorage.setItem(key, JSON.stringify({hasPassed: newPassed})).catch(e => console.log(e))
}
export default alpht =({navigation}) => {
function Check() {
if (results.includes(words[index])){
Alert.alert('Correct!','You are learning so well!');
if(index==7) {
if(count<=5)
{
setHasPassed(true).then(() => setshowpass(true))
// setshowpass(true);
}
else{
console.log(count)
Alert.alert('fail','fail');
}
}
if (index==7){
setndis(true);
setdis(true);
setidis(true);
}
else{
setndis(false);
setdis(true);
setidis(true);
}
}
else{
Alert.alert('Ops!','Looks like you went wrong somewhere. Try again!');
setcount(count+1);
setdis(true);
setndis(true);
if(count==5){
Alert.alert('Restest', 'Looks like you had way too many mistakes!')
setind(0);
setcount(0);
setdis(true);
}
}
}
const words=['ceket', 'çilek', 'elma', 'fare', 'öğretmen', 'otobüs', 'şemsiye', 'uçak'];
const [show, setshow]=useState('');
const [showpass, setshowpass]=useState(false);
useEffect(() => {
//console.log(auth().currentUser.uid);
setshow(true);
}, []);
useEffect(() => {
const getState = async () => {
const result = await hasPassed()
setshowpass(result ? result.hasPassed : false)
}
getState()
}, []);
console.log(auth().currentUser.uid)
if (showpass === false) {
// setshow(true)
console.log('hey');
return null
}
return (
//... other code
)
}
my user logs out using auth().signOut() by the way!
It would be great if this issue gets solved i am dealing with it for the past 4,5 days now!
I think this is the problem:
const key = auth().currentUser.uid + "hasPassed"
export const hasPassed = async () => {
return AsyncStorage.getItem(key).then(result => result != null ? JSON.parse(result) : undefined).catch(e => console.log(e))
}
export const setHasPassed = async (newPassed) => {
return AsyncStorage.setItem(key, JSON.stringify({hasPassed: newPassed})).catch(e => console.log(e))
}
key is defined at the top level, outside of the react lifecycle, and thus is subject to having stale values. auth().currentUser may change, the value of key will not (I think). Instead of storing key as a string, try storing it as a function:
// every time getKey is called it will get a fresh instance of currentUser
const getKey = ()=>auth().currentUser.uid + "hasPassed"
export const hasPassed = async () => {
return AsyncStorage.getItem(getKey()).
then(result => result != null ? JSON.parse(result) : undefined).
catch(e => console.log(e))
}
export const setHasPassed = async (newPassed) => {
return AsyncStorage.setItem(
getKey(),
JSON.stringify({hasPassed: newPassed})
).catch(e => console.log(e))
}
I don't know exactly what's going wrong in your code, but I believe that the piece of code in your useEffect is fetching the state of user A no matter who is logged in ( state persistence). try testing with user C. check out firebase state persistence in their official documentation. I hope I gave you some hints to solve this issue.

I lost props after reloading the page in react

I used axios in useEffect of my wrapper component and I sent the data as props to the other component "singleQuestionnaire", in singleQuestionnaire component, I destructured the data, in the first try, it works fine, but after reloading the page it doesn't work with an error : can not read property "map" of undefined
import React, { useEffect, useState } from "react";
import SingleQuestionnaire from "./SingleQuestionnaire";
import { fetchQuestions } from "../../../api/index";
const Questionnaires = ({ match }) => {
const [questions, setQuestions] = useState([]);
const pid = match.params.id;
const getQuestionnaire = async (pid) => {
try {
const { data } = await fetchQuestions(pid);
console.log(data.data, "action in component");
setQuestions(data.data);
} catch (error) {
console.log(error);
}
};
useEffect(() => {
getQuestionnaire(pid);
}, []);
console.log("all questions", questions);
return (
<div>
<SingleQuestionnaire questions={questions} setQuestions={setQuestions} />
</div>
);
};
export default Questionnaires;
and this is my singleQuestionnaire component:
import React, { useEffect, useState } from "react";
const SingleQuestionnaire = ({ questions, setQuestions }) => {
const [questionnaire, setQuestionnaire] = useState([]);
console.log(questions);
const { data } = questions;
console.log("data", data.farmInformationQuestionnaireData);
return <div>simple component</div>;
};
export default SingleQuestionnaire;
For the first time, in console I can see the data "data.data.farmInformationQuestionnaireData". It's an array but for the second time it's undefind.
because questions in SingleQuestionnaire is an empty array before we fetch
which causes an error here
const { data } = questions;
you can add a loading text because initially questions will be an empty array then it will be your res.data (assuming it's an object)
const SingleQuestionnaire = ({ questions, setQuestions }) => {
const [questionnaire, setQuestionnaire] = useState([]);
console.log(questions);
if(questions.length === 0 ) return <h1> Loading</h1>
const { data } = questions;
console.log("data", data.farmInformationQuestionnaireData);
return <div>simple component</div>;
};
it is happening because of the async API call. When you make an async call, the thread does not wait, it moves on and it starts executing other things.
Now your async call might be complete but your callback will not be executed until the stack is empty, that's just how javaScript works. I recommend you use some kind of loader gif or text
{questions ? <SingleQuestionnaire questions={questions} setQuestions={setQuestions} /> : <p>Loading...</p>}

conditional rendering with toast and usestate does not work with react

I have my state and I want to display the component if the value is true but in the console I receive the error message Cannot update during an existing state transition (such as within render). Render methods should be a pure function of props and state my code
import React, { useState} from "react";
import { useToasts } from "react-toast-notifications";
const Index = () => {
const [test, setTest]= useState(true);
const { addToast } = useToasts();
function RenderToast() {
return (
<div>
{ addToast('message') }
</div>
)}
return (
<div>
{test && <RenderToast /> }
</div>
)
}
You cannot set state during a render. And I'm guessing that addToast internally sets some state.
And looking at the docs for that library, you don't explicitly render the toasts. You just call addToast and then the <ToastProvider/> farther up in the tree shows them.
So to make this simple example works where a toast is shown on mount, you should use an effect to add the toast after the first render, and make sure your component is wrapped by <ToastProvider>
const Index = () => {
const { addToast } = useToasts();
useEffect(() => {
addToast('message')
}, [])
return <>Some Content here</>
}
// Example app that includes the toast provider
const MyApp = () => {
<ToastProvider>
<Index />
</ToastProvider>
}
how i can display the toast based on a variable for exemple display toast after receive error on backend?
You simply call addToast where you are handling your server communication.
For example:
const Index = () => {
const { addToast } = useToasts();
useEffect(() => {
fetchDataFromApi()
.then(data => ...)
.catch(error => addToast(`error: ${error}`))
}, [])
//...
}

Test conditionals and useEffect in React with Jest

I need to write a test with the following steps:
get user data on mount
get project details if it has selectedProject and clientId when they change
get pages details if it has selectedProject, clientId, and selectedPages when they change
render Content inside Switch
if doesn't have clientId, Content should return null
if doesn't have selectedProject, Content should return Projects
if doesn't have selectedPages, Content should return Pages
else Content should render Artboard
And the component looks like this:
import React, { useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import { getUserData } from "../../firebase/user";
import { selectProject } from "../../actions/projects";
import { getItem } from "../../tools/localStorage";
import { getProjectDetails } from "../../firebase/projects";
import { selectPages } from "../../actions/pages";
import Pages from "../Pages";
import Projects from "../Projects";
import Artboard from "../Artboard";
import Switch from "../Transitions/Switch";
import { getUserId, getClientId } from "../../selectors/user";
import { getSelectedProject } from "../../selectors/projects";
import { getSelectedPages, getPagesWithDetails } from "../../selectors/pages";
import { getPagesDetails } from "../../firebase/pages";
const cachedProject = JSON.parse(getItem("selectedProject"));
const cachedPages = JSON.parse(getItem("selectedPages"));
const Dashboard = () => {
const dispatch = useDispatch();
const userId = useSelector(getUserId);
const clientId = useSelector(getClientId);
const selectedProject = useSelector(getSelectedProject) || cachedProject;
const selectedPages = useSelector(getSelectedPages) || cachedPages;
const pagesWithDetails = useSelector(getPagesWithDetails);
useEffect(() => {
dispatch(
getUserData(userId)
);
cachedProject && selectProject(cachedProject);
cachedPages && selectPages(cachedPages);
}, []);
useEffect(() => {
if (selectedProject && clientId) {
dispatch(
getProjectDetails(
clientId,
selectedProject
)
);
}
}, [selectedProject, clientId]);
useEffect(() => {
if (selectedPages && selectedProject && clientId) {
const pagesWithoutDetails = selectedPages.filter(pageId => (
!Object.keys(pagesWithDetails).includes(pageId)
));
dispatch(
getPagesDetails(
selectedProject,
pagesWithoutDetails
)
);
}
}, [selectedPages, selectedProject, clientId]);
const Content = () => {
if (!clientId) return null;
if (!selectedProject) {
return <Projects key="projects" />;
}
if (!selectedPages) {
return <Pages key="pages" />;
}
return <Artboard key="artboard" />;
};
console.log("Update Dashboard")
return (
<Switch>
{Content()}
</Switch>
);
};
Where I use some functions to fetch data from firebase, some to dispatch actions, and some conditionals.
I'm trying to get deep into testing with Jest and Enzyme. When I was searching for testing approaches, testing useEffect, variables, and conditions, I haven't found anything. All I saw is testing if a text changes, if a button has get clicked, etc. but what about testing components which aren't really changing anything in the DOM, just loading data, and depending on that data, renders a component?
What's the question here? What have you tried? To me it seems pretty straightforward to test:
Use Enzymes mount or shallow to render the component and assign that to a variable and wrap it in a store provider so it has access to a redux store.
Use jest.mock to mock things you don't want to actually want to happen (like the dispatching of actions) or use something like redux-mock-store.
Use that component ".find" to get the actual button you want.
Assert that, given a specific redux state, it renders correctly.
Assert that actions are dispatched with the proper type and payload at the proper times.
You may need to call component.update() to force it to rerender within the enzyme test.
Let me know if you have more specific issues.
Good luck!

React Redux - passing arguments to event handler not working

Ok, I got a head scratcher I need a little bit of help with. The setup is that I have React/Redux app with a Categories page that reads a list of categories from an API, then lists them out. That part works fine. What I'm trying to do is pass in an event handler to each of the category child components that, when clicked, dispatches an action that toggles the state of the component, i.e., if the category is selected and clicked on, it will "unselect" it (which actually means deleting an entry from a database table called user_category), and if not selected, will "select" that category for that user (add an entry in the user_category table).
So I've got an onclick handler (handleCatClick) that is supposed to ultimately pass a categoryId and a userId to perform these operations. Unfortunately what I'm finding that even though these arguments are being passed to the function, they end up being undefined. So I'm not sure if I'm passing this function correctly or what exactly I've missed.
Everything works other than this - maybe you can help me spot the problem ;-)
Click here to view the database layout
Click here to see how the category page looks
The applicable pages in my app:
The architecture looks basically like this:
/views/[Categories]
- index.js (wrapper for the Categories Component)
- CategoriesComponent.jsx (should be self-explanatory)
[duck]
- index.js (just imports a couple of files & ties stuff together)
- operations.js (where my handleCatClick() method is)
- types.js (Redux constants)
- actions.js (Redux actions)
- reducers.js (Redux reducers)
[components]
[Category]
- index.jsx (the individual Category component)
/views/index.js(main Category page wrapper)
import { connect } from 'react-redux';
import CategoriesComponent from './CategoriesComponent';
import { categoriesOperations } from './duck'; // operations.js
const mapStateToProps = state => {
// current state properties passed down to LoginComponent (LoginComponent.js)
const { categoryArray } = state.categories;
return { categoryArray }
};
const mapDispatchToProps = (dispatch) => {
// all passed in from LoginOperations (operations.js)
const loadUserCategories = () => dispatch(categoriesOperations.loadUserCategories());
const handleCatClick = () => dispatch(categoriesOperations.handleCatClick());
return {
loadUserCategories,
handleCatClick
}
};
const CategoriesContainer = connect(mapStateToProps,mapDispatchToProps)(CategoriesComponent);
export default CategoriesContainer;
/views/CategoriesComponent.jsx (display layer for the Categories view)
import React from 'react';
import {Row,Col,Container, Form, Button} from 'react-bootstrap';
import {Link} from 'react-router-dom';
import './styles.scss';
import Category from './components/Category';
import shortid from 'shortid';
class CategoriesComponent extends React.Component {
constructor(props) {
super(props);
this.loadUserCats = this.props.loadUserCategories;
this.handleCatClick = this.props.handleCatClick;
}
componentWillMount() {
this.loadUserCats();
}
render() {
return (
<Container fluid className="categories nopadding">
<Row>
<Col xs={12}>
<div className="page-container">
<div className="title-container">
<h4>Pick your favorite categories to contine</h4>
</div>
<div className="content-container">
<div className="category-container">
{
this.props.categoryArray.map((item) => {
return <Category className="category" handleClick={this.props.handleCatClick} key={shortid.generate()} categoryData={item} />
})
}
</div>
</div>
</div>
</Col>
</Row>
</Container>
)
}
}
export default CategoriesComponent
/views/Categories/components/index.jsx (Single Category Component)
import React from 'react';
import {Row,Col,Container, Form, Button} from 'react-bootstrap';
import './styles.scss';
import Img from 'react-image';
class Category extends React.Component {
constructor(props) {
super(props);
this.state = {
categoryName: this.props.categoryData.category_name,
categoryImg: this.props.categoryData.category_img,
categoryId: this.props.categoryData.category_id,
userId: this.props.categoryData.user_id,
selected: this.props.categoryData.user_id !== null,
hoverState: ''
}
this.hover = this.hover.bind(this);
this.hoverOff = this.hoverOff.bind(this);
this.toggleCat = this.toggleCat.bind(this);
}
toggleCat() {
// the onClick handler that is supposed to
// pass categoryId and userId. When I do a
// console.log(categoryId, userId) these two values
// show up no problem...
const {categoryId, userId} = this.state;
this.props.handleClick(categoryId, userId);
}
hover() {
this.setState({
hoverState: 'hover-on'
});
}
hoverOff() {
this.setState({
hoverState: ''
});
}
render() {
const isSelected = (baseCat) => {
if(this.state.selected) {
return baseCat + " selected";
}
return baseCat;
}
return (
<div className={"category" + ' ' + this.state.hoverState} onClick={this.toggleCat} onMouseOver={this.hover} onMouseOut={this.hoverOff}>
<div className={this.state.selected ? "category-img selected" : "category-img"}>
<Img src={"/public/images/category/" + this.state.categoryImg} className="img-fluid" />
</div>
<div className="category-title">
<h5 className={this.state.selected ? "bg-primary" : "bg-secondary"}>{this.state.categoryName}</h5>
</div>
</div>
);
}
}
export default Category;
/views/Categories/duck/operations.js (where I tie it all together)
// operations.js
import fetch from 'cross-fetch';
import Actions from './actions';
import Config from '../../../../config';
const loadCategories = Actions.loadCats;
const selectCat = Actions.selectCat;
const unSelectCat = Actions.unSelectCat;
const localState = JSON.parse(localStorage.getItem('state'));
const userId = localState != null ? localState.userSession.userId : -1;
const loadUserCategories = () => {
return dispatch => {
return fetch(Config.API_ROOT + 'usercategories/' + userId)
.then(response => response.json())
.then(json => {
dispatch(loadCategories(json));
});
}
}
const handleCatClick = (categoryId, categoryUserId) => {
// HERE IS WHERE I'M HAVING A PROBLEM:
// for whatever reason, categoryId and categoryUserId
// are undefined here even though I'm passing in the
// values in the Category component (see 'toggleCat' method)
var params = {
method: categoryUserId !== null ? 'delete' : 'post',
headers: {'Content-Type':'application/json'},
body: JSON.stringify(
{
"category_id": categoryId,
user_id: categoryUserId !== null ? categoryUserId : userId
}
)
};
const toDispatch = categoryUserId !== null ? unSelectCat : selectCat;
return dispatch => {
return fetch(Config.API_ROOT + 'usercategories/', params)
.then(response => response.json())
.then(json => {
dispatch(toDispatch(json));
});
}
}
export default {
loadUserCategories,
handleCatClick
}
The problem that I am having:
So I'm thinking I'm either not referencing handleCatClick correctly, or I'm somehow not passing the categoryId and userId correctly so that when it finally gets to handleCatClick(categoryId, categoryUserId) in operations.js, it ends up as undefined. It's probably something simple but I can't spot it. NOTE: I haven't included files like the types.js or reducers.js, because they seem to be outside the scope of the problem, but if you need them please let me know. Thanks in advance for your help!
Try this changes: Add params to these handlers
const handleCatClick = (categoryId, categoryUserId) => dispatch(categoriesOperations.handleCatClick(categoryId, categoryUserId));
and
return <Category className="category" handleClick={(categoryId, categoryUserId) => this.props.handleCatClick(categoryId, categoryUserId)} key={shortid.generate()} categoryData={item} />

Categories