How to implement useContext (React Native, Expo) to control weather unit settings? - node.js

I am trying to implement the ability to switch between imperial and metric units using the useContext() hook. So far, I have not had much success. I have read the React useContext() documentation and followed this article. This is my current JavaScript source code that currently does not function as intended:
App.js - where context provider is located
import React from 'react';
import WeatherScreen from './components/screens/WeatherScreen';
import { CurrentUnitContext } from './components/hooks/CurrentUnitContext.js';
export default App = () => {
return (
<CurrentUnitContext.Provider
value={"imperial"}
>
<WeatherScreen />
</CurrentUnitContext.Provider>
);
};
CurrentUnitContext.js - Intended for createContext()
import { createContext } from 'react';
/**
* WIP custom hook for setting current unit
*
* todo - change weather units here
* set imperial (F), metric (C), and maybe standard (K)
*/
export default CurrentUnitContext = createContext();
/**
export default CurrentUnitContext = createContext({
theCurrentUnit: "imperial",
setTheCurrentUnit: () => {},
});
*/
UnitSwitch.js - Intended to contain the component to set the units with user feedback.
import React, { useState, useEffect, useContext } from 'react';
import { Text, TouchableOpacity, View } from 'react-native';
import { CurrentUnitContext } from '../../hooks/CurrentUnitContext.js';
import { tailwind, fontConfig } from '../../../tailwind.js';
/**
* Unit Switcher child component. Based on switch's boolean
* value useState hook isEnabled, set useEffect hook currentUnit
* to either "imperial" or "metric".
*
* #param - switchHeader: Displays the unit switch header
* text passed from WeatherContent.js
* #param - imperialUnits: Text passed from WeatherContext.js for
* imperial unit button string.
* #param - metricUnits: Text passed from WeatherContext.js for
* metric unit button string.
*/
export default UnitSwitch = ({ imperialUnits, metricUnits, switchHeader }) => {
const [theCurrentUnitSetting, setTheCurrentUnitSetting] = useState("imperial");
return (
<View style={tailwind('bg-gray-300 border-t-2 p-3 w-64 h-48')}>
<Text style={tailwind('text-center text-22fz')}>
{switchHeader}
</Text>
<View style={tailwind('flex-1 justify-center items-center')}>
<TouchableOpacity
style={tailwind('')}
onPress={() => setTheCurrentUnitSetting("metric")}
>
<Text style={tailwind('')}>
{metricUnits}
</Text>
</TouchableOpacity>
</View>
</View>
);
};
getWeather.js - Hook designed to fetch weather
import { useState, useEffect, useContext } from 'react';
import useLocation from '../hooks/useLocation.js';
import { CurrentUnitContext } from './CurrentUnitContext.js';
/**
* #getWeather - if permission was granted in useLocation() hook,
* uses Object theLocation containing lat. and long. coordinates as
* decimal number values. Returns the currentWeather. useEffect()
* relies on the value of Object theLocation.
*
* #WEATHER_API_KEY - accessed from clientSecret directory,
* hidden for repo security and must be manually provided.
* #currentWeather - useState hook to store JSON result of
* current weather data.
* #baseWeatherUrl - initial OpenWeatherMap API access string
* #weatherUrl - Full OpenWeatherMap API access string
*/
export default getWeather = () => {
const theLocation = useLocation();
const theCurrentUnit = useContext(CurrentUnitContext);
const [currentWeather, setCurrentWeather] = useState();
useEffect(() => {
const { WEATHER_API_KEY } = require("../clientSecret/openWeather.json");
let baseWeatherUrl = 'https://api.openweathermap.org/data/2.5/weather?',
weatherUrl = "";
//console.log(theLocation); //confirm we are getting location, uncomment if needed
if (theLocation !== undefined) {
weatherUrl = `${baseWeatherUrl}lat=${theLocation.latitude}&lon=${theLocation.longitude}&units=${theCurrentUnit}&appid=${WEATHER_API_KEY}`;
console.log(weatherUrl); //uncomment if needed
const fetchWeather = async () => {
try {
const response = await fetch(weatherUrl);
const result = await response.json();
if (response.ok) {
setCurrentWeather(result);
} else {
alert(result.message);
};
} catch (error) {
console.log(error);
alert(error.message);
} finally {
console.log("async function fetchWeather() has been run."); //API rate call confirmation
};
};
//API calls must not occur more than once every minute.
fetchWeather();
};
}, [theLocation, theCurrentUnit]);
return currentWeather;
};
As shown right now, the error message that appears is:
TypeError: undefined is not an object (evaluating >'_CurrentUnitContext.CurrentUnitContext.Provider')
This error is located at:
in App (created by ExpoRoot)
in ExpoRoot (at renderApplication.js:45)
in RCTView (at View.js:34)
in View (at AppContainer.js:106)
in RCTView (at View.js:34)
in View (at AppContainer.js:132)
in AppContainer (at renderApplication.js:39)

I was assisted, in CurrentUnitContext.js, changing
export default CurrentUnitContext = createContext();
to
export let CurrentUnitContext = createContext();
fixed the error.

Related

How to determine if "click" or "box-select" was used with Streamlit/Plotly to return data from chart to Streamlit

I'm not a Javascript/Typescript/React dev. I'm hacking my way through this for a work project.
I'm using Streamlit, with plotly.
I'm hacking the basic code from streamlit-plotly-events.
I was trying to have the click or box-select information passed back with the data selected via the plotlyEventHandler() (see code below.) However, both this.props.args["click_event"] and this.props.args["select_event"] are true, regardless of whether you use box-select in the plotly chart, or click a single data point in the chart.
I thought of assuming if there is only one data point, then it was a click - but you can box select only one data point.
// import React, {useState,useEffect} from "react"
import React, { ReactNode } from "react"
//import React from "react"
import {
StreamlitComponentBase,
withStreamlitConnection,
Streamlit,
// ComponentProps,
} from "streamlit-component-lib"
import Plot from "react-plotly.js"
class StreamlitPlotlyEventsCapture extends StreamlitComponentBase {
public render = (): ReactNode => {
// Pull Plotly object from args and parse
const plot_obj = JSON.parse(this.props.args["plot_obj"]);
const override_height = this.props.args["override_height"];
const override_width = this.props.args["override_width"];
// Event booleans
const click_event = this.props.args["click_event"];
const select_event = this.props.args["select_event"];
const hover_event = this.props.args["hover_event"];
Streamlit.setFrameHeight(override_height);
return (
<Plot
data={plot_obj.data}
layout={plot_obj.layout}
config={plot_obj.config}
frames={plot_obj.frames}
onClick={click_event ? this.plotlyEventHandler : function(){}}
onSelected={select_event ? this.plotlyEventHandler : function(){}}
onHover={hover_event ? this.plotlyEventHandler : function(){}}
style={{width: override_width, height: override_height}}
className="stPlotlyChart"
/>
)
}
/** Click handler for plot. */
private plotlyEventHandler = (data: any) => {
// Build array of points to return
var clickedPoints: Array<any> = [];
//const util = require('util')//#33333 used with util.inspect(arrayItem) below
// I dont know why we can't directly use "this.variables" in the clickedPoints.push
// but we can't, so we create the variables here.
var wasClicked = this.props.args["click_event"];
var wasSelected = this.props.args["select_event"];
var wasHovered = this.props.args["hover_event"];
data.points.forEach(function (arrayItem: any) {
// console.log(util.inspect(arrayItem, {maxArrayLength: null, depth:null }))
clickedPoints.push({
// I dont know why we can't directly use "this.variables" here, but we can't
// so we use the variables created above.
clicked:wasClicked,
selected:wasSelected,
hovered:wasHovered,
x: arrayItem.x,
y: arrayItem.y,
curveNumber: arrayItem.curveNumber,
pointNumber: arrayItem.pointNumber,
pointIndex: arrayItem.pointIndex
})
});
// Return array as JSON to Streamlit
Streamlit.setComponentValue(JSON.stringify(clickedPoints))
}
}
export default withStreamlitConnection(StreamlitPlotlyEventsCapture)

React Native Redux Component not rerendering on Redux State Change

I am working on a shopping basket component in React Native. The content and price of the basket get saved in the global redux store. When the user selects an item, an action gets dispatched to add the item to the basket and to update the total basket price.
The UI however does not get updated on this global state change.
My Reducer
const INITIAL_STATE = {
basket: [],
basketPrice: 0,
};
const mainReducer = (state = INITIAL_STATE, action) => {
const stateCpy = state;
switch (action.type) {
case 'SET_BASKET':
stateCpy.basket = action.payload
return stateCpy;
case 'ADD_TO_BASKET':
stateCpy.basket.push(action.payload)
return stateCpy
case 'REMOVE_FROM_BASKET':
let tempItems = stateCpy.basket
for (var x = 0; x < stateCpy.basket.length; x++) {
if (stateCpy.basket[x]._id === action.payload) {
tempItems.splice(x, 1)
break;
}
}
stateCpy.basket = tempItems
return stateCpy
case 'SET_BASKET_PRICE':
stateCpy.basketPrice = action.payload
console.log(stateCpy.basketPrice)
return stateCpy
default:
return state
}
};
export default mainReducer
My Actions
const setBasket = basket => ({
type: 'SET_BASKET',
payload: basket,
});
const addToBasket = item => ({
type: 'ADD_TO_BASKET',
payload: item,
});
const removeFromBasket = item_id => ({
type: 'REMOVE_FROM_BASKET',
payload: item_id,
});
const setBasketPrice = price => ({
type: 'SET_BASKET_PRICE',
payload: price,
});
export default actions = {
setBasket,
addToBasket,
removeFromBasket,
setBasketPrice
}
My UI Component
...
import { useSelector, useDispatch } from 'react-redux'
export const RestaurantView = ({ navigation, route }) => {
const basket = useSelector((state) => state.basket)
const basketPrice = useSelector((state) => state.basketPrice)
const dispatch = useDispatch()
...
function calcBasketPrice(){
let tempBasketPrice = 0
basket.forEach(element => {
tempBasketPrice += element.price
});
return tempBasketPrice
}
function addToBasket(item) {
dispatch(actions.setBasketPrice(calcBasketPrice() + item.price))
dispatch(actions.addToBasket(item))
}
return ( <View>
<ItemCard onPress={addToBasket}> </ItemCard>
<Text style={{ textAlign: "right", padding: 15, fontSize: 20 }}> {basketPrice}</Text>
</View>)
}
When logging the basketPrice to console in the reducer, it logs the correct, updated value on each press/dispatch but there no changes in the UI. When a local state change is made to force a rerender, it renders with the correct value from the global store.
Your stateCpy variable is actually not a copy, but just a reference to state - so your reducer is actually modifying the old redux state instead of creating a new one.
Since this is a very outdated style of Redux and in modern Redux createSlice reducers it is totally okay to modify state, I would recommend you not to fix this in legacy Redux, but to take a look at modern Redux - which is also less error-prone and about 1/4 of the code you would write with legacy Redux.
Rewriting your reducer is not a lot of work and in the long run you will really benefit from the (since 2019) new style.
Take a look at the official Redux tutorial

How to clear undo history in CodeMirror 6?

editor.clearHistory(); works in CodeMirror 5 I believe, but what about CodeMirror 6?
interface HistoryConfig {
/**
The minimum depth (amount of events) to store. Defaults to 100.
*/
minDepth?: number;
/**
The maximum time (in milliseconds) that adjacent events can be
apart and still be grouped together. Defaults to 500.
*/
newGroupDelay?: number;
}
/**
Create a history extension with the given configuration.
*/
declare function history(config?: HistoryConfig): Extension;
import { EditorView } from '#codemirror/view';
import { EditorState } from '#codemirror/state';
import { history } from '#codemirror/commands';
const state = EditorState.create({
doc: 'my source code',
extensions: [history({ minDepth: 10 })],
});
const view = new EditorView({
parent: document.querySelector('#editor'),
state,
});

Adding a new card to the DB

I am trying to add a new card to the database.
I have a page in React that when a user fills the form and clicks "Next", a new user is created and added to the Database.
The next page is Business Information he needs to fill, but when he clicks on "Create Card" it doesn't do anything and the Database remains empty.
This is the code for my BusinessRegistrationPage.js:
import {toast} from 'react-toastify';
import {Container} from 'react-bootstrap';
import SimpleRegistrationComp from "../components/simple-registration/SimpleRegistrationComp";
import CreateCardComp from '../components/my-cards/CreateCardComp';
import {useState} from 'react';
function BusinessRegistrationPage () {
const[isStep1,setIsStep1]= useState(true);
return <Container>
{isStep1&&
<SimpleRegistrationComp clickHandler={registerUser}
text="Next" >
</SimpleRegistrationComp>
}
{
!isStep1&& <CreateCardComp clickHandler={createCard} ></CreateCardComp>
}
</Container>;
function registerUser(data) {
registerNewAccount(data,
(data)=>{
if(data._id){
toast('Account Created Successfully');
setIsStep1(false);
}
else
{
toast('Eror Acount was not created');
}
});
}
function createCard() {
alert('create card');
}
}
export default BusinessRegistrationPage;```

Facing Issue : Warning: React.createElement: type is invalid -- expected a string

I'm using useReducer, Context and Provider in my app but I'm facing this issue please anyone faced this issue so please let me know the solution for it
Warning: React.createElement: type is invalid -- expected a string
(for built-in components) or a class/function (for composite
components) but got: %s.%s%s, undefined, You likely forgot to export
your component from the file it's defined in, or you might have mixed
up default and named imports.
Check your code at App.js:20.,
in _default (at withExpoRoot.js:20)
in RootErrorBoundary (at withExpoRoot.js:19)
in ExpoRootComponent (at renderApplication.js:35)
in RCTView (at View.js:45)
in View (at AppContainer.js:98)
in RCTView (at View.js:45)
in View (at AppContainer.js:115)
in AppContainer (at renderApplication.js:34)
- node_modules\react-native\Libraries\YellowBox\YellowBox.js:59:8 in error
- node_modules\expo\build\environment\muteWarnings.fx.js:26:24 in error
- node_modules\react\cjs\react.development.js:188:36 in warningWithoutStack
- node_modules\react\cjs\react.development.js:603:32 in warning
- node_modules\react\cjs\react.development.js:1730:14 in createElementWithValidation
* App.js:20:5 in _default
- node_modules\react-native\Libraries\Renderer\oss\ReactNativeRenderer-dev.js:9473:27
in renderWithHooks
- node_modules\react-native\Libraries\Renderer\oss\ReactNativeRenderer-dev.js:11994:6
in mountIndeterminateComponent
- ... 18 more stack frames from framework internals
Warning: React.createElement: type is invalid -- expected a string
(for built-in components) or a class/function (for composite
components) but got: %s.%s%s, undefined, You likely forgot to export
your component from the file it's defined in, or you might have mixed
up default and named imports.
Check your code at App.js:20.,
in _default (at withExpoRoot.js:20)
in RootErrorBoundary (at withExpoRoot.js:19)
in ExpoRootComponent (at renderApplication.js:35)
in RCTView (at View.js:45)
in View (at AppContainer.js:98)
in RCTView (at View.js:45)
in View (at AppContainer.js:115)
in AppContainer (at renderApplication.js:34)
- node_modules\react-native\Libraries\YellowBox\YellowBox.js:59:8 in error
- node_modules\expo\build\environment\muteWarnings.fx.js:26:24 in error
- node_modules\react\cjs\react.development.js:188:36 in warningWithoutStack
- node_modules\react\cjs\react.development.js:603:32 in warning
- node_modules\react\cjs\react.development.js:1730:14 in createElementWithValidation
* App.js:20:5 in _default
- node_modules\react-native\Libraries\Renderer\oss\ReactNativeRenderer-dev.js:9473:27
in renderWithHooks
- node_modules\react-native\Libraries\Renderer\oss\ReactNativeRenderer-dev.js:11994:6
in mountIndeterminateComponent
- ... 21 more stack frames from framework internals
Invariant Violation: Element type is invalid: expected a string (for
built-in components) or a class/function (for composite components)
but got: undefined. You likely forgot to export your component from
the file it's defined in, or you might have mixed up default and named
imports.
Check the render method of _default.
- node_modules\react-native\Libraries\Renderer\oss\ReactNativeRenderer-dev.js:5716:10
in createFiberFromTypeAndProps
- node_modules\react-native\Libraries\Renderer\oss\ReactNativeRenderer-dev.js:5744:4
in createFiberFromElement
- ... 22 more stack frames from framework internals
Warning: %s: Error boundaries should implement
getDerivedStateFromError(). In that method, return a state update to
display an error message or fallback UI., RootErrorBoundary
- node_modules\react-native\Libraries\YellowBox\YellowBox.js:59:8 in error
- node_modules\expo\build\environment\muteWarnings.fx.js:26:24 in error
- ... 28 more stack frames from framework internals
App.js
import React from 'react';
import { createAppContainer } from 'react-navigation';
import { createStackNavigator } from 'react-navigation-stack';
import IndexScreen from './src/screens/IndexScreen';
import { Provider } from './src/context/BlogContext';
const navigator = createStackNavigator({
Index: IndexScreen
}, {
initialRouteName: 'Index',
defaultNavigationOptions: {
title: 'Blogs'
}
});
const App = createAppContainer(navigator);
export default () => {
return (
<Provider>
<App />
</Provider>
);
};
BlogContext.js
import React, { useReducer } from 'react';
export default (reducer, actions, initialState) => {
const Context = React.createContext();
const Provider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialState);
const boundActions = {};
for(let key in actions){
boundActions[key] = actions[key](dispatch);
}
return(
<Context.Provider value={{state, ...boundActions}}>
{children}
</Context.Provider>
);
}
return(Context, Provider);
};
You have an issue regarding BlogContext. You are not using it correctly.
You export it as default a function while in App.js, you use a named import.
BlogContext should be like this:
import React, { useReducer } from 'react';
export default (reducer, actions, initialState) => {
const Context = React.createContext();
const Provider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialState);
const boundActions = {};
for(let key in actions){
boundActions[key] = actions[key](dispatch);
}
return(
<Context.Provider value={{state, ...boundActions}}>
{children}
</Context.Provider>
);
}
//return an object with two keys
return {Context, Provider};
};
and App.js should use that accordingly:
import blogContext from './src/context/BlogContext';
/**
...code ...
**/
//here you create your provider by calling the function imported from BlogContext
//with the expected arguments
const { Provider } = blogContext(reducer, actions, initialState)
/**
**/
export default () => {
return (
<Provider>
<App />
</Provider>
);
};

Resources