I am trying to implement the following case with two screens:
Adding players screen
Game screen: Challenges are read from JSON file and filled in with random variables during runtime. Example: in "p(1) drinks sips(2,4)" p(1) will be replaced with a random choice of a list of players, and sips will be replaced by either 2, 3 or 4 sips.
I am using stack navigation, passing the result of adding players in the player screen to the challenge screen. It works perfectly fine when I first start the game, but when I pop screen 2, and go back to screen 2, the challenges appear in random order (as expected), but they are already filled in.
The initialstate reads the non-filled in challenges from a JSON file, so why are the challenges already filled in when I rerender screen 2.
Gamescreen:
import React, { useState } from 'react';
import { StyleSheet, View, Text, TouchableWithoutFeedback, Alert } from 'react-native';
import ChallengeComponent from '../components/gameScreenComponents/challengeComponent'
import { shuffle } from '../helpers/shuffle'
import { fillInSips, fillInChars, fillInNumbers, fillInPlayers, fillInChoice} from '../helpers/challengeRegex'
const GameScreen = ({ route, navigation }) => {
const playersNeeded = (challenge) => {
return 0
}
const popChallengeHandler = () => {
if (challenges.length > 1) {
let newChallenges = [...challenges];
newChallenges.shift()
processedChallenges = playChallenge(newChallenges)
setChallenges(processedChallenges)
}
else {
setChallenges([])
}
}
const playChallenge = (currentChallenges) => {
currentChallenges[0] = fillInChallenge(currentChallenges[0]);
currentChallenges[0]['nextRounds'].forEach(round => currentChallenges.splice(round[0],0,{
initialRound: round[1],
nextRounds: []
}))
return currentChallenges
}
const fillInChallenge = (challenge) => {
return fillInChoice(players)
}
const endGameHandler = () => {
Alert.alert("Ending game")
}
const players = route.params;
const [challenges, setChallenges] = useState(() => {
const initialState = playChallenge(shuffle(require('../data/challenges.json').deck.filter(challenge => players.length >= playersNeeded(challenge))));
return initialState;
});
if (challenges.length > 0)
return (<ChallengeComponent pressHandler={popChallengeHandler} text={challenges[0]['initialRound']} navigation={navigation}/>)
else
return (<ChallengeComponent pressHandler={endGameHandler} text="Het spel is afgelopen" navigation={navigation}/>)
}
export default GameScreen;
Navigator:
import React from 'react';
import { NavigationContainer } from '#react-navigation/native';
import { createStackNavigator } from '#react-navigation/stack';
import AddPlayerScreen from './screens/addPlayers';
import GameScreen from './screens/game';
console.disableYellowBox = true;
const RootStack = createStackNavigator();
const App = () => {
return (
<NavigationContainer>
<RootStack.Navigator screenOptions={{headerShown: true}}>
<RootStack.Screen name="AddPlayers" component={AddPlayerScreen} />
<RootStack.Screen name="Game" component={GameScreen} />
</RootStack.Navigator>
</NavigationContainer>
);
};
export default App;
You can use this event listeners to doing some updates or else
navigation.addListener('blur', () => {console.log('focused Out')});
navigation.addListener('focus', () => {console.log('focused')});
Related
I'm using the Intersection Observer API in React to add some animations. I am adding as Intersection Entries some elements.
The problem is that I have the app in multiple languages, and due to the implementation that the tool I am using to translate has, I need to wrap all my components into React.Suspense to wait for languages to load.
When useEffect queries for the elements, they aren't still in the DOM, and therefore they are not assigned as entries.
This is my custom hook:
hooks/useObserver.js
import { useState } from "react";
import { useEffect, useRef } from "react";
export function useObserver(config = {}) {
const [elements, setElements] = useState([]);
const [entries, setEntries] = useState([]);
const observer = useRef(
new IntersectionObserver(observedEntries => {
setEntries(observedEntries);
}, config)
);
useEffect(() => {
const { current: currentObserver } = observer;
currentObserver.disconnect();
if (elements.length > 0) {
elements.forEach(el => currentObserver.observe(el));
}
return () => {
if (currentObserver) {
currentObserver.disconnect();
}
};
}, [elements]);
return { observer: observer.current, setElements, entries };
}
and this is my main component:
App.jsx
import Header from "./components/Header";
import Hero from "./components/Hero";
import Footer from "./components/Footer";
import { Loader } from "./components/shared/Loader";
import { useObserver } from "./hooks/useObserver";
import { useEffect, Suspense } from "react";
function App() {
const { entries, setElements } = useObserver({});
useEffect(() => {
const sections = document.querySelectorAll("section.animated-section");
setElements(sections);
};
}, [setElements]);
useEffect(() => {
entries.forEach(entry => {
entry.target.classList.toggle("section-visible", entry.isIntersecting);
});
}, [entries]);
return (
<Suspense fallback={<Loader />}>
<Header />
<Hero />
<Footer />
</Suspense>
);
}
export default App;
I tried to set a timeout to wait some seconds and then add the elements as entries, and it works correctly:
useEffect(() => {
const observeElements = () => {
const sections = document.querySelectorAll("section.animated-section");
setElements(sections);
};
const observeElementsTimeout = setTimeout(observeElements, 3000);
return () => clearTimeout(observeElementsTimeout)
}, [setElements]);
I want to know if:
There is a way to know when React.Suspense is ready
There is a better approach to solve my problem
Thanks in advance!!
I'm currently making a simple web frontend with react using react-autosuggest to search a specified user from a list. I want to try and use the Autosuggest to give suggestion when the user's type in the query in the search field; the suggestion will be based on username of github profiles taken from github user API.
What I want to do is to separate the AutoSuggest.jsx and then import it into Main.jsx then render the Main.jsx in App.js, however it keeps giving me 'TypeError: _ref2 is undefined' and always refer to my onChange function of AutoSuggest.jsx as the problem.
Below is my App.js code:
import './App.css';
import 'bootstrap/dist/css/bootstrap.min.css';
import Header from './views/header/Header';
import Main from './views/main/Main';
import Footer from './views/footer/Footer';
const App = () => {
return (
<>
<Header/>
<Main/> <- the autosuggest is imported in here
<Footer/>
</>
);
}
export default App;
Below is my Main.jsx code:
import React, { useState } from 'react';
import Container from 'react-bootstrap/Container';
import Row from 'react-bootstrap/Row';
import axios from 'axios';
import { useEffect } from 'react';
import AutoSuggest from '../../components/AutoSuggest';
const Main = () => {
const [userList, setUserList] = useState([]);
useEffect(() => {
axios.get('https://api.github.com/users?per_page=100')
.then((res) => setUserList(res.data))
.catch((err) => console.log(err));
}, [])
return (
<Container>
<br/>
<Row>
<AutoSuggest userList={userList} placeHolderText={'wow'} />
</Row>
</Container>
);
}
export default Main;
Below is my AutoSuggest.jsx code:
import React, { useState } from "react";
import Autosuggest from 'react-autosuggest';
function escapeRegexCharacters(str) {
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
function getSuggestions(value, userList) {
const escapedValue = escapeRegexCharacters(value.trim());
if (escapedValue === '') {
return [];
}
const regex = new RegExp('^' + escapedValue, 'i');
return userList.filter(user => regex.test(user.login));
}
function getSuggestionValue(suggestion) {
return suggestion.name;
}
function renderSuggestion(suggestion) {
return (
<span>{suggestion.name}</span>
);
}
const AutoSuggest = ({userList, placeHolderText}) => {
const [value, setValue] = useState('');
const [suggestions, setSuggestions] = useState([]);
const onChange = (event, { newValue, method }) => { <- error from console always refer here, I'm not quite sure how to handle it..
setValue(newValue);
};
const onSuggestionsFetchRequested = ({ value }) => {
setValue(getSuggestions(value, userList))
};
const onSuggestionsClearRequested = () => {
setSuggestions([]);
};
const inputProps = {
placeholder: placeHolderText,
value,
onChange: () => onChange()
};
return (
<Autosuggest
suggestions={suggestions}
onSuggestionsFetchRequested={() => onSuggestionsFetchRequested()}
onSuggestionsClearRequested={() => onSuggestionsClearRequested()}
getSuggestionValue={() => getSuggestionValue()}
renderSuggestion={() => renderSuggestion()}
inputProps={inputProps} />
);
}
export default AutoSuggest;
The error on browser (Firefox) console:
I have no idea what does the error mean or how it happened and therefore unable to do any workaround.. I also want to ask if what I do here is already considered a good practice or not and maybe some inputs on what I can improve as well to make my code cleaner and web faster. Any input is highly appreciated, thank you in advance!
you have to write it like this... do not use the arrow function in inputProps
onChange: onChange
I'm quite new to React Hooks/Context so I'd appreciate some help. Please don' t jump on me with your sharp teeth. I Checked other solutions and some ways i've done this before but can't seem to get it here with the 'pick from the list' way.
SUMMARY
I need to get the municipios list of names inside of my const 'allMunicipios'(array of objects) inside of my Search.js and then display a card with some data from the chosen municipio.
TASK
Get the data from eltiempo-net REST API.
Use Combobox async element from Elastic UI to choose from list of municipios.
Display Card (from elastic UI too) with some info of chosen municipio.
It has to be done with function components / hooks. No classes.
I'd please appreciate any help.
WHAT I'VE DONE
I've created my reducer, context and types files in a context folder to fecth all data with those and then access data from the component.
I've created my Search.js file. Then imported Search.js in App.js.
I've accesed the REST API and now have it in my Search.js
PROBLEM
Somehow I'm not beeing able to iterate through the data i got.
Basically i need to push the municipios.NOMBRE from api to the array const allMunicipios in my search.js component. But when i console log it it gives me undefined. Can;t figure out why.
I'll share down here the relevant code/components. Thanks a lot for whoever takes the time.
municipiosReducer.js
import {
SEARCH_MUNICIPIOS,
CLEAR_MUNICIPIOS,
GET_MUNICIPIO,
GET_WEATHER,
} from "./types";
export default (state, action) => {
switch (action.type) {
case SEARCH_MUNICIPIOS:
return {
...state,
municipios: action.payload,
};
case GET_MUNICIPIO:
return {
...state,
municipio: action.payload,
};
case CLEAR_MUNICIPIOS:
return {
...state,
municipios: [],
};
case GET_WEATHER: {
return {
...state,
weather: action.payload,
};
}
default:
return state;
}
};
municipiosContext.js
import { createContext } from "react";
const municipiosContext = createContext();
export default municipiosContext;
MunicipiosState.js
import React, { createContext, useReducer, Component } from "react";
import axios from "axios";
import MunicipiosContext from "./municipiosContext";
import MunicipiosReducer from "./municipiosReducer";
import {
SEARCH_MUNICIPIOS,
CLEAR_MUNICIPIOS,
GET_MUNICIPIO,
GET_WEATHER,
} from "./types";
const MunicipiosState = (props) => {
const initialState = {
municipios: [],
municipio: {},
};
const [state, dispatch] = useReducer(MunicipiosReducer, initialState);
//Search municipios
//In arrow functions 'async' goes before the parameter.
const searchMunicipios = async () => {
const res = await axios.get(
`https://www.el-tiempo.net/api/json/v2/provincias/08/municipios`
// 08 means barcelona province. This should give me the list of all its municipios
);
dispatch({
type: SEARCH_MUNICIPIOS,
payload: res.data.municipios,
});
};
//Get Municipio
const getMunicipio = async (municipio) => {
const res = await axios.get(
`https://www.el-tiempo.net/api/json/v2/provincias/08/municipios/${municipio.CODIGOINE}`
//CODIGOINE is in this REST API kind of the ID for each municipio.
//I intent to use this later to get the weather conditions from each municipio.
);
dispatch({ type: GET_MUNICIPIO, payload: res.municipio });
};
const dataMunicipiosArray = [searchMunicipios];
//Clear Municipios
const clearMunicipios = () => {
dispatch({ type: CLEAR_MUNICIPIOS });
};
return (
<MunicipiosContext.Provider
value={{
municipios: state.municipios,
municipio: state.municipio,
searchMunicipios,
getMunicipio,
clearMunicipios,
dataMunicipiosArray,
}}
>
{props.children}
</MunicipiosContext.Provider>
);
};
export default MunicipiosState;
Search.js
import "#elastic/eui/dist/eui_theme_light.css";
import "#babel/polyfill";
import MunicipiosContext from "../contexts/municipiosContext";
import MunicipiosState from "../contexts/MunicipiosState";
import { EuiComboBox, EuiText } from "#elastic/eui";
import React, { useState, useEffect, useCallback, useContext } from "react";
const Search = () => {
const municipiosContext = useContext(MunicipiosContext);
const { searchMunicipios, municipios } = MunicipiosState;
useEffect(() => {
return municipiosContext.searchMunicipios();
}, []);
const municipiosFromContext = municipiosContext.municipios;
const bringOneMunicipio = municipiosContext.municipios[0];
let municipiosNames = municipiosFromContext.map((municipio) => {
return { label: `${municipio.NOMBRE}` };
});
console.log(`municipiosFromContext`, municipiosFromContext);
console.log(`const bringOneMunicipio:`, bringOneMunicipio);
console.log(`municipiosNames:`, municipiosNames);
const allMunicipios = [
{ label: "santcugat" },
{ label: "BARCELONETA" },
{ label: "BARCE" },
];
const [selectedOptions, setSelected] = useState([]);
const [isLoading, setLoading] = useState(false);
const [options, setOptions] = useState([]);
let searchTimeout;
const onChange = (selectedOptions) => {
setSelected(selectedOptions);
};
// combo-box
const onSearchChange = useCallback((searchValue) => {
setLoading(true);
setOptions([]);
clearTimeout(searchTimeout);
// eslint-disable-next-line react-hooks/exhaustive-deps
searchTimeout = setTimeout(() => {
// Simulate a remotely-executed search.
setLoading(false);
setOptions(
municipiosNames.filter((option) =>
option.label.toLowerCase().includes(searchValue.toLowerCase())
)
);
}, 1200);
}, []);
useEffect(() => {
// Simulate initial load.
onSearchChange("");
}, [onSearchChange]);
return (
<div>
<EuiComboBox
placeholder="Search asynchronously"
async
options={options}
selectedOptions={selectedOptions}
isLoading={isLoading}
onChange={onChange}
onSearchChange={onSearchChange}
/>
<button>Lista de municipios</button>
</div>
);
};
export default Search;
also the
Home.js
import React, { useState } from "react";
import { EuiComboBox, EuiText } from "#elastic/eui";
// import { DisplayToggles } from "../form_controls/display_toggles";
import "#babel/polyfill";
import "#elastic/eui/dist/eui_theme_light.css";
import Search from "./Search";
import MunicipioCard from "./MunicipioCard";
const Home = () => {
return (
<div>
<EuiText grow={false}>
<h1>Clima en la provincia de Barcelona</h1>
<h2>Por favor seleccione un municipio</h2>
</EuiText>
<Search />
<MunicipioCard />
</div>
);
};
export default Home;
App.js
import "#babel/polyfill";
import "#elastic/eui/dist/eui_theme_light.css";
import { EuiText } from "#elastic/eui";
import React from "react";
import Home from "./components/Home";
import MunicipiosState from "./contexts/MunicipiosState";
import "./App.css";
function App() {
return (
<MunicipiosState>
<div className="App">
<EuiText>
<h1>App Component h1</h1>
</EuiText>
<Home />
</div>
</MunicipiosState>
);
}
export default App;
You are using forEach and assigning the returned value to a variable, however forEach doesn't return anything. You should instead use map
let municipiosNames = municipiosFromContext.map((municipio) => {
return `label: ${municipio.NOMBRE}`;
});
As per your comment:
you data is loaded asynchronously, so it won't be available on first render and since functional components depend on closures, you onSearchChange function takes the value from the closure at the time of creation and even if you have a setTimeout within it the updated value won't reflect
The solution here is to add municipiosFromContext as a dependency to useEffect
const onSearchChange = useCallback((searchValue) => {
setLoading(true);
setOptions([]);
clearTimeout(searchTimeout);
// eslint-disable-next-line react-hooks/exhaustive-deps
searchTimeout = setTimeout(() => {
// Simulate a remotely-executed search.
setLoading(false);
setOptions(
municipiosNames.filter((option) =>
option.label.toLowerCase().includes(searchValue.toLowerCase())
)
);
}, 1200);
}, [municipiosFromContext]);
useEffect(() => {
// Simulate initial load.
onSearchChange("");
}, [onSearchChange]);
I am new to React native development. I want to understand how useState works.
import React from 'react'
import { View, Text } from 'react-native'
import Styles from './AdsStyle'
import { useContextSelector } from 'use-context-selector'
import { StateContext } from './State'
import { useState } from 'react'
import { useRef } from 'react'
import moment from 'moment'
import { useEffect } from 'react'
//To-do: Ads cx countdown timer.
const CountDownTimer = () => {
const myState = useContextSelector(StateContext, (state) => state.myState)
const timerRef = useRef(null)
const [timer, setTime] = useState(myState == null ? 0: myState.endTime)
if(myState === null){
clearInterval(timerRef.current)
return null
}
useEffect(()=>{
updateTimer()
return () => {
clearTimeout(timerRef.current)
}
})
let remainingTime
const updateTimer = ()=>{
timerRef.current = setInterval(()=>{
remainingTime = timer - 1000
if(remainingTime <0){
clearInterval(timerRef.current)
remainingTime = 0
}
setTime(remainingTime)
},1000)
}
const renderCountDownTimer = () => {
return (
<View>
<Text>{moment(timer).format('mm:ss')} </Text>
</View>
)}
return renderCountDownTimer()
}
export default React.memo(CountDownTimer, () => true)
I am simply building the count down timer. There are two issues i am facing for now.
When control comes to const [timer, setTime] = useState(myState == null ? 0: myState.endTime) it simply goes to first line to re-execute the component again. Every thing works fine if i have hardcoded value in useState.
if i move the code const [timer, setTime] = useState(myState == null ? 0: myState.endTime) after the
if(myState === null){
clearInterval(timerRef.current)
return null
}
then react fails with an error that Rendered more hooks than during the previous render
Can someone pls explain both these cases.
Thanks in advance
Hey everyone pretty new to React hooks. I am simply trying to set some reviews that I retrieve from Firebase but cant seem to get it working. I tried a few solutions and I am struggling to get it working any help would be appreciated.
import React, {useContext, useEffect, useState} from 'react';
import firebase from "firebase";
import ReviewsContext from "./review-context";
const Reviews = () => {
const db = firebase.firestore();
let reviews = useContext(ReviewsContext);
let [reviewsLoaded, setReviewsLoaded] = useState(false);
function getReviews(){
db.collection('reviews')
.get()
.then((snapshot) => {
let dataArray = [];
snapshot.docs.forEach(doc => {
dataArray.push(doc.data());
});
reviews = dataArray;
setReviewsLoaded(true);
console.log('reviews', reviews); // logs the correct amount of reviews
})
}
function renderReviews() {
console.log('renderReviews reviewsLoaded', reviewsLoaded); // is true
console.log('renderReviews reviews length', reviews.length); // is 0
if(reviewsLoaded) {
reviews.map((data) => {
return (
<li key={data.name}>
<h3>{data.name}</h3>
<p>{data.position}</p>
</li>
)
});
}
else {
return false
}
}
useEffect(() => {
getReviews(); // this seems to fire before renderReviews
}, []);
return (
<div>
<ul>
{renderReviews()}
</ul>
</div>
)
};
export default Reviews;
In this case, the context should be stateful. The way you're doing it currently won't work since context on render will always revert to reviews being empty. Your Provider component that gives that ReviewContext should be patterned like below.
import React, { createContext, useState } from "react"
const ReviewContext = createContext()
const ReviewProvider = ({children}) => {
const [reviews, setReviews] = useState([])
return (
<ReviewContext.Provider value={{
reviews: reviews,
setReviews: reviews => setReviews(reviews),
}}>
{children}
</ReviewContext.Provider>
)
}
export default ReviewProvider
export { ReviewContext }
Now, you may do const { reviews, setReviews } = useContext(ReviewContext); Just call setReviews whenever you want to update reviews in the context.
It's actually stated in the docs as well as I searched it. https://reactjs.org/docs/context.html#dynamic-context